From 8eab3b445cba7ed34fddc6767e27c850f476962d Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 17:58:09 +0900 Subject: [PATCH 01/42] =?UTF-8?q?[feature]=20#4=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=EA=B5=AC=EC=A1=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/kernellabs/kernellabs/application/.gitkeep | 0 src/main/java/com/kernellabs/kernellabs/domain/.gitkeep | 0 src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep | 0 src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/.gitkeep create mode 100644 src/main/java/com/kernellabs/kernellabs/domain/.gitkeep create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep diff --git a/src/main/java/com/kernellabs/kernellabs/application/.gitkeep b/src/main/java/com/kernellabs/kernellabs/application/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/kernellabs/kernellabs/domain/.gitkeep b/src/main/java/com/kernellabs/kernellabs/domain/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep b/src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep b/src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep new file mode 100644 index 0000000..e69de29 From 609aeeff138ca06ae63f28b8b819dba7c6918692 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 19:05:38 +0900 Subject: [PATCH 02/42] =?UTF-8?q?[feature]=20#8=20geminiDto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/.gitkeep | 0 .../application/VacationService.java | 7 ++ .../kernellabs/infrastructure/.gitkeep | 0 .../external/GeminiApiClient.java | 10 +++ .../infrastructure/external/GeminiDto.java | 69 +++++++++++++++++++ .../kernellabs/presentation/.gitkeep | 0 .../controller/ResponseEntity.java | 4 ++ .../controller/VacationController.java | 20 ++++++ .../dto/request/RouteRequest.java | 4 ++ .../dto/response/RouteResponse.java | 4 ++ 10 files changed, 118 insertions(+) delete mode 100644 src/main/java/com/kernellabs/kernellabs/application/.gitkeep create mode 100644 src/main/java/com/kernellabs/kernellabs/application/VacationService.java delete mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiDto.java delete mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/.gitkeep b/src/main/java/com/kernellabs/kernellabs/application/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/kernellabs/kernellabs/application/VacationService.java b/src/main/java/com/kernellabs/kernellabs/application/VacationService.java new file mode 100644 index 0000000..dd2837d --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/VacationService.java @@ -0,0 +1,7 @@ +package com.kernellabs.kernellabs.application; + +import org.springframework.stereotype.Service; + +@Service +public class VacationService { +} diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep b/src/main/java/com/kernellabs/kernellabs/infrastructure/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java new file mode 100644 index 0000000..43d7aa8 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java @@ -0,0 +1,10 @@ +package com.kernellabs.kernellabs.infrastructure.external; + +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class GeminiApiClient { +} diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiDto.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiDto.java new file mode 100644 index 0000000..3f0a1b3 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiDto.java @@ -0,0 +1,69 @@ +package com.kernellabs.kernellabs.infrastructure.external; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class GeminiDto { + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Request { + @JsonProperty("contents") + private List contents; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Content { + + @JsonProperty("parts") + private List parts; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Part { + @JsonProperty("text") + private String text; + } + } + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Response { + private List candidates; + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Candidate { + private Content content; + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Content { + private List parts; + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Part { + private String text; + } + } + } + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep b/src/main/java/com/kernellabs/kernellabs/presentation/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java new file mode 100644 index 0000000..a452b5d --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java @@ -0,0 +1,4 @@ +package com.kernellabs.kernellabs.presentation.controller; + +public class ResponseEntity { +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java new file mode 100644 index 0000000..df5332d --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java @@ -0,0 +1,20 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.kernellabs.kernellabs.application.VacationService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.request.RouteRequest; +import com.kernellabs.kernellabs.presentation.dto.response.RouteResponse; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class VacationController { + + + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java new file mode 100644 index 0000000..9e267c8 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java @@ -0,0 +1,4 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +public class RouteRequest { +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java new file mode 100644 index 0000000..01e6b3d --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java @@ -0,0 +1,4 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +public class RouteResponse { +} From 646e846f4d5553d076cf005449ac9a4b49ef27f4 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 19:19:09 +0900 Subject: [PATCH 03/42] =?UTF-8?q?[cd]=20#9=20-=20cd=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/delploy-to-server.yml | 35 ++++++++++++++++++++----- .gitignore | 3 ++- src/main/resources/application.yml | 11 ++++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/.github/workflows/delploy-to-server.yml b/.github/workflows/delploy-to-server.yml index ce687fe..fffa59f 100644 --- a/.github/workflows/delploy-to-server.yml +++ b/.github/workflows/delploy-to-server.yml @@ -1,11 +1,10 @@ -name: CI/CD for Spring Boot +name: KernelLabs CICD on: push: - branches: - - main - - develop - + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] jobs: build: name: Build and Test @@ -74,8 +73,25 @@ jobs: name: Deploy to Server runs-on: ubuntu-latest needs: docker - steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Create application-prod.yml + run: | + mkdir -p src/main/resources + echo "${{ secrets.PROPERTIES_PROD }}" > src/main/resources/application-prod.yml + shell: bash + + - name: Transfer application-prod.yml to Server + uses: appleboy/scp-action@v0.1.6 + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + key: ${{ secrets.SERVER_SSH_KEY }} + source: "src/main/resources/application-prod.yml" + target: "/home/${{ secrets.SERVER_USER }}/app-config/" + - name: Deploy via SSH uses: appleboy/ssh-action@v1.0.3 with: @@ -83,9 +99,14 @@ jobs: username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} script: | + mkdir -p /home/${{ secrets.SERVER_USER }}/app-config/ + docker pull ${{ secrets.DOCKER_USERNAME }}/spring-app:latest docker stop spring-app || true docker rm spring-app || true docker run -d --name spring-app -p 80:8080 \ -e TZ=Asia/Seoul \ - ${{ secrets.DOCKER_USERNAME }}/spring-app:latest \ No newline at end of file + -v /home/${{ secrets.SERVER_USER }}/app-config/application-prod.yml:/app/config/application-prod.yml \ + -e SPRING_PROFILES_ACTIVE=prod \ + ${{ secrets.DOCKER_USERNAME }}/spring-app:latest + diff --git a/.gitignore b/.gitignore index 2f51786..23c0cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -188,4 +188,5 @@ $RECYCLE.BIN/ *.lnk # env -application-local.yml \ No newline at end of file +application-local.yml +application-prod.yml \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2e71195..584c4e4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,7 @@ spring: profiles: active: local datasource: - url: jdbc:mysql://localhost:3306/kernellabs?allowPublicKeyRetrieval=true&useSSL=false + url: ${MYSQL_URL} username: ${MYSQL_USERNAME} password: ${MYSQL_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver @@ -24,4 +24,11 @@ spring: config: activate: on-profile: local - import: application-local.yml \ No newline at end of file + import: application-local.yml + +--- +spring: + config: + activate: + on-profile: prod + import: application-prod.yml \ No newline at end of file From a2a89837d1f135182049ce9127dd710a34ac43ad Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 19:29:08 +0900 Subject: [PATCH 04/42] =?UTF-8?q?[CD]=20#9=20-=20Dockerfile=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/delploy-to-server.yml | 26 ++++++++----------------- Dockerfile | 13 +++++-------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/.github/workflows/delploy-to-server.yml b/.github/workflows/delploy-to-server.yml index fffa59f..360342e 100644 --- a/.github/workflows/delploy-to-server.yml +++ b/.github/workflows/delploy-to-server.yml @@ -53,6 +53,12 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 + - name: Create application-prod.yml for Docker build + run: | + mkdir -p src/main/resources + echo "${{ secrets.PROPERTIES_PROD }}" > src/main/resources/application-prod.yml + shell: bash + - name: Log in to Docker Hub uses: docker/login-action@v2 with: @@ -77,21 +83,6 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Create application-prod.yml - run: | - mkdir -p src/main/resources - echo "${{ secrets.PROPERTIES_PROD }}" > src/main/resources/application-prod.yml - shell: bash - - - name: Transfer application-prod.yml to Server - uses: appleboy/scp-action@v0.1.6 - with: - host: ${{ secrets.SERVER_HOST }} - username: ${{ secrets.SERVER_USER }} - key: ${{ secrets.SERVER_SSH_KEY }} - source: "src/main/resources/application-prod.yml" - target: "/home/${{ secrets.SERVER_USER }}/app-config/" - - name: Deploy via SSH uses: appleboy/ssh-action@v1.0.3 with: @@ -99,14 +90,13 @@ jobs: username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} script: | - mkdir -p /home/${{ secrets.SERVER_USER }}/app-config/ - + # mkdir -p /home/${{ secrets.SERVER_USER }}/app-config/ docker pull ${{ secrets.DOCKER_USERNAME }}/spring-app:latest docker stop spring-app || true docker rm spring-app || true docker run -d --name spring-app -p 80:8080 \ -e TZ=Asia/Seoul \ - -v /home/${{ secrets.SERVER_USER }}/app-config/application-prod.yml:/app/config/application-prod.yml \ + # -v /home/${{ secrets.SERVER_USER }}/app-config/application-prod.yml:/app/config/application-prod.yml \ -e SPRING_PROFILES_ACTIVE=prod \ ${{ secrets.DOCKER_USERNAME }}/spring-app:latest diff --git a/Dockerfile b/Dockerfile index c2b6f8f..6fe5022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,19 @@ -# πŸ”Ή 1단계: λΉŒλ“œ ν™˜κ²½ (Gradle λΉŒλ“œ) FROM gradle:8.5-jdk17 AS builder WORKDIR /app -# Gradle 캐싱을 μœ„ν•΄ μ˜μ‘΄μ„± κ΄€λ ¨ 파일 λ¨Όμ € 볡사 COPY build.gradle settings.gradle ./ COPY gradle gradle RUN gradle build || return 0 # Gradle μΊμ‹œ 적용 -# 전체 μ†ŒμŠ€ 볡사 ν›„ λΉŒλ“œ μ‹€ν–‰ COPY . . RUN gradle clean build -x test - -# πŸ”Ή 2단계: μ‹€ν–‰ ν™˜κ²½ (JVM만 포함) FROM eclipse-temurin:17-jdk WORKDIR /app -# λΉŒλ“œλœ JAR νŒŒμΌμ„ μ‹€ν–‰ ν™˜κ²½μœΌλ‘œ 볡사 COPY --from=builder /app/build/libs/*.jar app.jar -# μ»¨ν…Œμ΄λ„ˆ μ‹€ν–‰ μ‹œ Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰ -ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file +COPY --from=builder /app/src/main/resources/application-prod.yml /app/config/application-prod.yml + +ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-Dspring.config.location=classpath:/,file:/app/config/", "-jar", "app.jar"] + +EXPOSE 8080 \ No newline at end of file From 7ddc1d7d9f80774870d06424ca1ac0bdc8f33baa Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 19:30:44 +0900 Subject: [PATCH 05/42] =?UTF-8?q?[cd]=20#9=20-=20deploy=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/delploy-to-server.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/delploy-to-server.yml b/.github/workflows/delploy-to-server.yml index 360342e..26e4f71 100644 --- a/.github/workflows/delploy-to-server.yml +++ b/.github/workflows/delploy-to-server.yml @@ -47,7 +47,6 @@ jobs: name: Build and Push Docker Image runs-on: ubuntu-latest needs: build - if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' steps: - name: Checkout Repository From e7487596d626e7cbb5317fae7bb2100315cb4498 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 19:35:11 +0900 Subject: [PATCH 06/42] =?UTF-8?q?[fix]=20#9=20-=20deploy=20=EC=A4=84?= =?UTF-8?q?=EB=B0=94=EA=BF=88=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/delploy-to-server.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/delploy-to-server.yml b/.github/workflows/delploy-to-server.yml index 26e4f71..1c218d8 100644 --- a/.github/workflows/delploy-to-server.yml +++ b/.github/workflows/delploy-to-server.yml @@ -89,13 +89,12 @@ jobs: username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} script: | - # mkdir -p /home/${{ secrets.SERVER_USER }}/app-config/ docker pull ${{ secrets.DOCKER_USERNAME }}/spring-app:latest docker stop spring-app || true docker rm spring-app || true docker run -d --name spring-app -p 80:8080 \ -e TZ=Asia/Seoul \ - # -v /home/${{ secrets.SERVER_USER }}/app-config/application-prod.yml:/app/config/application-prod.yml \ -e SPRING_PROFILES_ACTIVE=prod \ ${{ secrets.DOCKER_USERNAME }}/spring-app:latest + From 766e019af729e72bc6de0faa65456fa2e0ae9a46 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 20:08:03 +0900 Subject: [PATCH 07/42] =?UTF-8?q?[feat]=20#8=20-=20Ai=20route=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20controller=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../controller/ResponseEntity.java | 4 ---- .../controller/VacationController.java | 12 +++++++++++- .../presentation/dto/request/RouteRequest.java | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) delete mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java diff --git a/build.gradle b/build.gradle index d7c6cf1..c95642b 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java deleted file mode 100644 index a452b5d..0000000 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ResponseEntity.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.kernellabs.kernellabs.presentation.controller; - -public class ResponseEntity { -} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java index df5332d..4118d36 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java @@ -1,7 +1,9 @@ package com.kernellabs.kernellabs.presentation.controller; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.kernellabs.kernellabs.application.VacationService; @@ -9,12 +11,20 @@ import com.kernellabs.kernellabs.presentation.dto.request.RouteRequest; import com.kernellabs.kernellabs.presentation.dto.response.RouteResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController @RequiredArgsConstructor public class VacationController { + private final VacationService vacationService; - + @GetMapping("/vacations") + public ResponseEntity> getVacationRoute( + @RequestBody @Valid RouteRequest routeRequest + ) { + RouteResponse routeResponse = vacationService.getVacationRoute(routeRequest); + return ResponseEntity.ok(ApiResponse.success(routeResponse)); + } } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java index 9e267c8..c2000ff 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java @@ -1,4 +1,22 @@ package com.kernellabs.kernellabs.presentation.dto.request; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor public class RouteRequest { + @NotNull + private String startDate; + @NotNull + private String duration; + @NotNull + private String vibe; + @NotNull + private String interests; + private String companion; + private String budget; } From a8c27fbf20e744058f33bc77e63fabdee6b88030 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 20:29:12 +0900 Subject: [PATCH 08/42] =?UTF-8?q?[feat]=20#8=20-=20webclient=20config=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../global/config/WebClientConfig.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/global/config/WebClientConfig.java diff --git a/build.gradle b/build.gradle index c95642b..abec230 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-webflux' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/WebClientConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/WebClientConfig.java new file mode 100644 index 0000000..ba60e07 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/global/config/WebClientConfig.java @@ -0,0 +1,16 @@ +package com.kernellabs.kernellabs.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + + @Bean + public WebClient geminiWebClient() { + return WebClient.builder() + .baseUrl("https://generativelanguage.googleapis.com") + .build(); + } +} \ No newline at end of file From 5d495d0e37b08e3919092e9104173b969c4b1650 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 20:49:43 +0900 Subject: [PATCH 09/42] =?UTF-8?q?[feat]=20#8=20-=20AI=20=EB=A3=A8=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EC=B2=9C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/VacationService.java | 56 +++++++++++++++++++ .../external/GeminiApiClient.java | 50 +++++++++++++++++ .../dto/response/RouteResponse.java | 6 ++ src/main/resources/application.yml | 4 ++ 4 files changed, 116 insertions(+) diff --git a/src/main/java/com/kernellabs/kernellabs/application/VacationService.java b/src/main/java/com/kernellabs/kernellabs/application/VacationService.java index dd2837d..dcf5506 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/VacationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/VacationService.java @@ -2,6 +2,62 @@ import org.springframework.stereotype.Service; +import com.kernellabs.kernellabs.infrastructure.external.GeminiApiClient; +import com.kernellabs.kernellabs.presentation.dto.request.RouteRequest; +import com.kernellabs.kernellabs.presentation.dto.response.RouteResponse; + +import lombok.RequiredArgsConstructor; + @Service +@RequiredArgsConstructor public class VacationService { + + private final GeminiApiClient geminiApiClient; + + public RouteResponse getVacationRoute(RouteRequest routeRequest) { + String prompt = String.format( + """ + 당신은 μ˜μ„±κ΅° μ „λ¬Έ μ—¬ν–‰ ν”Œλž˜λ„ˆμž…λ‹ˆλ‹€. + μ‚¬μš©μžκ°€ μš”μ²­ν•œ 정보에 κΈ°λ°˜ν•˜μ—¬ λŒ€ν•œλ―Όκ΅­ 경상뢁도 μ˜μ„±κ΅°μœΌλ‘œμ˜ μƒμ„Έν•œ μ—¬ν–‰ κ³„νšμ„ μ§œμ£Όμ„Έμš”. + + --- μ‚¬μš©μž μš”μ²­ 정보 ---= + μ‹œμž‘μΌ: %s + κΈ°κ°„: %s + μ„ ν˜Έ λΆ„μœ„κΈ°: %s + 관심 ν™œλ™: %s + λ™λ°˜μž: %s (없을 경우 λ¬΄μ‹œ) + μ˜ˆμ‚°: %s (없을 경우 λ¬΄μ‹œ) + ------------------------- + + μ—¬ν–‰ κ³„νšμ€ λ‹€μŒ μ§€μ‹œμ‚¬ν•­μ„ μ—„κ²©ν•˜κ²Œ μ§€μΌœμ„œ JSON 객체둜만 μ œκ³΅ν•΄μ•Ό ν•©λ‹ˆλ‹€. + μ„€λͺ…은 ν•œκ΅­μ–΄λ‘œ μž‘μ„±ν•˜κ³ , λ§ˆν¬λ‹€μš΄(json 블둝 포함)μ΄λ‚˜ λ‹€λ₯Έ μ„€λͺ… 없이 μˆœμˆ˜ν•œ JSON ν…μŠ€νŠΈλ§Œ 응닡해야 ν•©λ‹ˆλ‹€. + 각 μž₯μ†ŒλŠ” 'name', 'address', 'activity', 'estimated_cost', 'estimated_time' 을 λ°˜λ“œμ‹œ 포함해야 ν•©λ‹ˆλ‹€. + μ˜ˆμƒ λΉ„μš©μ€ '무료', '저렴함', '보톡', 'λΉ„μŒˆ' λ˜λŠ” ꡬ체적인 κ°€κ²©λŒ€λ₯Ό ν•œκ΅­μ–΄λ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”. + ν™œλ™ λ‚΄μš©κ³Ό μž₯μ†Œ μ„€λͺ…은 ꡬ체적이고 λ§€λ ₯적으둜 μž‘μ„±ν•΄ μ£Όμ„Έμš”. + μ—¬ν–‰ κ³„νšμ€ μ΅œμ†Œ 1일 이상 κ΅¬μ„±λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. + + 응닡 ν˜•μ‹ μ˜ˆμ‹œ: + { + "plan": [ + { + "day": 1, + "description": "9μ‹œ 일정", + "places": [ + { "name": "μž₯μ†Œ 이름", "address": "μ£Όμ†Œ", "activity": "ν•  것", "estimated_cost": "μ˜ˆμƒ λΉ„μš©", "estimated_time": "μ˜ˆμƒ μ‹œκ°„" } + ] + } + ] + } + """, + routeRequest.getStartDate(), + routeRequest.getDuration(), + routeRequest.getVibe(), + routeRequest.getInterests(), + routeRequest.getCompanion(), + routeRequest.getBudget() + ); + String answer = geminiApiClient.askGemini(prompt); + return RouteResponse.builder().route(answer).build(); + } + } diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java index 43d7aa8..1359aef 100644 --- a/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiApiClient.java @@ -1,10 +1,60 @@ package com.kernellabs.kernellabs.infrastructure.external; +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; +@Slf4j @Component @RequiredArgsConstructor public class GeminiApiClient { + + private final WebClient geminiWebClient; + + @Value("${gemini.api.key}") + private String apiKey; + + public String askGemini(String question) { + + GeminiDto.Request request = GeminiDto.Request.builder() + .contents(List.of( + GeminiDto.Request.Content.builder() + .parts(List.of( + GeminiDto.Request.Content.Part.builder() + .text(question) + .build() + )) + .build() + )) + .build(); + + GeminiDto.Response geminiResponse = geminiWebClient.post() + .uri("/v1beta/models/gemini-2.0-flash:generateContent?key=" + apiKey) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .bodyValue(request) + .retrieve() + .bodyToMono(GeminiDto.Response.class) + .onErrorResume(e -> { + log.error("Gemini 호좜 μ‹€νŒ¨", e); + return Mono.empty(); + }) + .block(); + + try { + return geminiResponse.getCandidates().get(0).getContent().getParts().get(0).getText(); + } catch (Exception e) { + log.error("Gemini λ‹΅λ³€ parsing μ‹€νŒ¨", e); + return "닡변을 생성할 수 μ—†μŠ΅λ‹ˆλ‹€"; + } + + } + } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java index 01e6b3d..b0f451c 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/RouteResponse.java @@ -1,4 +1,10 @@ package com.kernellabs.kernellabs.presentation.dto.response; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder public class RouteResponse { + private String route; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 584c4e4..da494d9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,6 +19,10 @@ logging: level: org.hibernate.sql: debug +gemini: + api: + key: ${GEMINI_API_KEY} + --- spring: config: From 501da2cb34fd50a3028d8484f8aaf1ccbc951d73 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 20:58:01 +0900 Subject: [PATCH 10/42] =?UTF-8?q?[feat]=20#8=20-=20=EA=B4=80=EA=B4=91?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=EC=B6=94=EC=B2=9CAI=20prompt=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/kernellabs/application/VacationService.java | 5 +++++ .../kernellabs/presentation/dto/request/RouteRequest.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/kernellabs/kernellabs/application/VacationService.java b/src/main/java/com/kernellabs/kernellabs/application/VacationService.java index dcf5506..dd168e9 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/VacationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/VacationService.java @@ -23,8 +23,10 @@ public RouteResponse getVacationRoute(RouteRequest routeRequest) { --- μ‚¬μš©μž μš”μ²­ 정보 ---= μ‹œμž‘μΌ: %s κΈ°κ°„: %s + μ‹œκ°„λŒ€: %s μ„ ν˜Έ λΆ„μœ„κΈ°: %s 관심 ν™œλ™: %s + ꡐ톡: %s λ™λ°˜μž: %s (없을 경우 λ¬΄μ‹œ) μ˜ˆμ‚°: %s (없을 경우 λ¬΄μ‹œ) ------------------------- @@ -34,6 +36,7 @@ public RouteResponse getVacationRoute(RouteRequest routeRequest) { 각 μž₯μ†ŒλŠ” 'name', 'address', 'activity', 'estimated_cost', 'estimated_time' 을 λ°˜λ“œμ‹œ 포함해야 ν•©λ‹ˆλ‹€. μ˜ˆμƒ λΉ„μš©μ€ '무료', '저렴함', '보톡', 'λΉ„μŒˆ' λ˜λŠ” ꡬ체적인 κ°€κ²©λŒ€λ₯Ό ν•œκ΅­μ–΄λ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”. ν™œλ™ λ‚΄μš©κ³Ό μž₯μ†Œ μ„€λͺ…은 ꡬ체적이고 λ§€λ ₯적으둜 μž‘μ„±ν•΄ μ£Όμ„Έμš”. + 각 μž₯μ†Œ μ‚¬μ΄λŠ” ꡐ톡 μ†Œμš”μ‹œκ°„μ„ μ°Έκ³ ν•΄μ„œ μ°Ύμ•„μ£Όμ„Έμš”. μ—¬ν–‰ κ³„νšμ€ μ΅œμ†Œ 1일 이상 κ΅¬μ„±λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. 응닡 ν˜•μ‹ μ˜ˆμ‹œ: @@ -51,8 +54,10 @@ public RouteResponse getVacationRoute(RouteRequest routeRequest) { """, routeRequest.getStartDate(), routeRequest.getDuration(), + routeRequest.getTime(), routeRequest.getVibe(), routeRequest.getInterests(), + routeRequest.getTransportation(), routeRequest.getCompanion(), routeRequest.getBudget() ); diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java index c2000ff..6d14520 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/RouteRequest.java @@ -14,9 +14,13 @@ public class RouteRequest { @NotNull private String duration; @NotNull + private String time; + @NotNull private String vibe; @NotNull private String interests; + @NotNull + private String transportation; private String companion; private String budget; } From d011d1f196fea74c44893b2bea8af4c87ecaf6f9 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 21:35:43 +0900 Subject: [PATCH 11/42] [fix] #15 - swagger version upgrade --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index abec230..6bef8bf 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-webflux' compileOnly 'org.projectlombok:lombok' From 233b14f3e32310d16ce84bde8c356dd474c5e1e9 Mon Sep 17 00:00:00 2001 From: HoyiTT Date: Fri, 27 Jun 2025 22:37:41 +0900 Subject: [PATCH 12/42] =?UTF-8?q?[feat]=20#13=20=EC=9B=8C=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=9E=A5=EC=86=8C=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/WorkService.java | 77 +++++++++++++++++++ .../kernellabs/kernellabs/domain/Work.java | 43 +++++++++++ .../exception/GlobalExceptionHandler.java | 2 +- .../infrastructure/WorkRepository.java | 8 ++ .../controller/VacationController.java | 20 ++++- .../controller/WorkController.java | 28 +++++++ .../dto/request/SurveyRequest.java | 14 ++++ .../dto/response/SurveyResponse.java | 23 ++++++ src/main/resources/application.yml | 3 +- 9 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/WorkService.java create mode 100644 src/main/java/com/kernellabs/kernellabs/domain/Work.java create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/WorkRepository.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/WorkController.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/SurveyRequest.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/WorkService.java b/src/main/java/com/kernellabs/kernellabs/application/WorkService.java new file mode 100644 index 0000000..0bbb058 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/WorkService.java @@ -0,0 +1,77 @@ +package com.kernellabs.kernellabs.application; + +import com.kernellabs.kernellabs.domain.Work; +import com.kernellabs.kernellabs.infrastructure.WorkRepository; +import com.kernellabs.kernellabs.presentation.dto.request.SurveyRequest; +import com.kernellabs.kernellabs.presentation.dto.response.SurveyResponse; +import java.util.List; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class WorkService { + private final WorkRepository placeRepo; + + + public SurveyResponse recommend(SurveyRequest req) { + // 1) μ‚¬μš©μž 벑터 계산 + double[] u = new double[8]; + // 업무 μœ ν˜• one-hot + u[0] = "A".equals(req.getQ1()) ? 1 : 0; // Dev + u[1] = "B".equals(req.getQ1()) ? 1 : 0; // Doc + u[2] = "C".equals(req.getQ1()) ? 1 : 0; // Meet + u[3] = "D".equals(req.getQ1()) ? 1 : 0; // Call + // μ‹œκ°„ + u[4] = switch(req.getQ2()) { + case "A" -> 0.1; + case "B" -> 0.3; + case "C" -> 0.5; + case "D" -> 0.75; + default -> 1.0; + }; + // 밀집도 + u[5] = switch(req.getQ3()) { + case "A" -> 0.0; + case "B" -> 0.33; + case "C" -> 0.66; + default -> 1.0; + }; + // μ‘°μš©β‡†κ΄€κ΄‘ + u[6] = switch(req.getQ4()) { + case "A" -> 0.0; + case "B" -> 0.5; + default -> 1.0; // Cβ†’1.0, Dβ†’1.0(ν˜Ήμ€ 0으둜 처리) + }; + // 독립 곡간 + u[7] = "A".equals(req.getQ5()) ? 1 : 0; + + // 2) DBμ—μ„œ λͺ¨λ“  μž₯μ†Œ κ°€μ Έμ™€μ„œ 내적 계산 + List places = placeRepo.findAll(); + Work best = null; + double bestScore = Double.NEGATIVE_INFINITY; + + for (Work p : places) { + double[] v = { + p.getWorkDev() ? 1:0, + p.getWorkDoc() ? 1:0, + p.getWorkMeet()? 1:0, + p.getWorkCall()?1:0, + p.getHoursNorm(), + p.getCrowdNorm(), + p.getLocationNorm(), + p.getPrivateAvailable()?1:0 + }; + double score = 0; + for (int i = 0; i < u.length; i++) { + score += u[i] * v[i]; + } + if (score > bestScore) { + bestScore = score; + best = p; + } + } + return SurveyResponse.from(best); + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Work.java b/src/main/java/com/kernellabs/kernellabs/domain/Work.java new file mode 100644 index 0000000..b06960f --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/Work.java @@ -0,0 +1,43 @@ +package com.kernellabs.kernellabs.domain; + + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "work") +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Work { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; // μž₯μ†Œλͺ… + + @Column(columnDefinition = "TEXT") + private String description; // μ„€λͺ… + private String type; // 곡유 μ˜€ν”ΌμŠ€/카페/μŠ€ν„°λ””μΉ΄νŽ˜ λ“± + private String address; // μ£Όμ†Œ + + private Boolean workDev; // κ°œλ°œΒ·μ½”λ”© 적합 μ—¬λΆ€ + private Boolean workDoc; // λ¬Έμ„œμž‘μ—… 적합 μ—¬λΆ€ + private Boolean workMeet; // νšŒμ˜Β·ν˜‘μ—… 적합 μ—¬λΆ€ + private Boolean workCall; // 톡화 적합 μ—¬λΆ€ + + private Double hoursNorm; // 지원 κ°€λŠ₯ μ‹œκ°„ μ •κ·œν™” (0.1~1.0) + private Double crowdNorm; // μ‚¬λžŒ 밀집도 (0.0~1.0) + private Double locationNorm; // μ‘°μš©β‡†κ΄€κ΄‘ (0.0~1.0) + private Boolean privateAvailable; // 독립 곡간 제곡 μ—¬λΆ€ +} diff --git a/src/main/java/com/kernellabs/kernellabs/global/exception/GlobalExceptionHandler.java b/src/main/java/com/kernellabs/kernellabs/global/exception/GlobalExceptionHandler.java index 408ee2f..8011b2d 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/kernellabs/kernellabs/global/exception/GlobalExceptionHandler.java @@ -13,7 +13,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -@RestControllerAdvice + @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/WorkRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/WorkRepository.java new file mode 100644 index 0000000..ba48d8e --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/WorkRepository.java @@ -0,0 +1,8 @@ +package com.kernellabs.kernellabs.infrastructure; + +import com.kernellabs.kernellabs.domain.Work; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WorkRepository extends JpaRepository { + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java index 4118d36..43679ae 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java @@ -1,9 +1,14 @@ package com.kernellabs.kernellabs.presentation.controller; +import com.kernellabs.kernellabs.application.WorkService; +import com.kernellabs.kernellabs.presentation.dto.request.SurveyRequest; +import com.kernellabs.kernellabs.presentation.dto.response.SurveyResponse; +import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.kernellabs.kernellabs.application.VacationService; @@ -27,4 +32,17 @@ public ResponseEntity> getVacationRoute( RouteResponse routeResponse = vacationService.getVacationRoute(routeRequest); return ResponseEntity.ok(ApiResponse.success(routeResponse)); } + + @RestController + @RequestMapping("/api/work") + @AllArgsConstructor + public static class WorkController { + private final WorkService recService; + + @PostMapping("/recommend") + public ResponseEntity> recommend(@RequestBody SurveyRequest req) { + SurveyResponse surveyResponse= recService.recommend(req); + return ResponseEntity.ok(ApiResponse.success(surveyResponse)); + } + } } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/WorkController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/WorkController.java new file mode 100644 index 0000000..e9822c3 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/WorkController.java @@ -0,0 +1,28 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import com.kernellabs.kernellabs.application.WorkService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.request.SurveyRequest; +import com.kernellabs.kernellabs.presentation.dto.response.SurveyResponse; +import lombok.AllArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +@AllArgsConstructor +public class WorkController { + + private final WorkService workService; + + @PostMapping("/recommend") + public ResponseEntity> recommend(@RequestBody SurveyRequest req) { + SurveyResponse surveyResponse = workService.recommend(req); + return ResponseEntity.ok(ApiResponse.success(surveyResponse)); + + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/SurveyRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/SurveyRequest.java new file mode 100644 index 0000000..2484030 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/SurveyRequest.java @@ -0,0 +1,14 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SurveyRequest { + private String q1; // A,B,C,D + private String q2; // A~E + private String q3; // A~D + private String q4; // A~D + private String q5; // A(예), B(μ•„λ‹ˆμ˜€) +} \ No newline at end of file diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java new file mode 100644 index 0000000..518aea3 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java @@ -0,0 +1,23 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import com.kernellabs.kernellabs.domain.Work; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SurveyResponse { + private String name; // μž₯μ†Œλͺ… + private String description; // μ„€λͺ… + private String type; // 곡유 μ˜€ν”ΌμŠ€/카페/μŠ€ν„°λ””μΉ΄νŽ˜ λ“± + private String address; // μ£Όμ†Œ + + public static SurveyResponse from(Work work) { + return new SurveyResponse( + work.getName(), + work.getDescription(), + work.getType(), + work.getAddress() + ); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index da494d9..8765a96 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,7 +17,8 @@ spring: format_sql: true logging: level: - org.hibernate.sql: debug + root: INFO # 전체 둜그 μ΅œμ†Œ INFO 이상 기둝 :contentReference[oaicite:0]{index=0} + org.springframework.web: INFO # Spring MVC λ””μŠ€νŒ¨μ²˜ μ„œλΈ”λ¦Ώ 둜그 :contentReference[oaicite:1]{index=1} gemini: api: From 09dfa46b0716128a1a0c3ef5d6753b02fc51cf8a Mon Sep 17 00:00:00 2001 From: HoyiTT Date: Fri, 27 Jun 2025 23:03:10 +0900 Subject: [PATCH 13/42] =?UTF-8?q?[feat]=20#13=20=EC=9B=8C=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=9E=A5=EC=86=8C=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?API=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/WorkService.java | 111 +++++++++--------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/kernellabs/kernellabs/application/WorkService.java b/src/main/java/com/kernellabs/kernellabs/application/WorkService.java index 0bbb058..e1b50ce 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/WorkService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/WorkService.java @@ -4,7 +4,7 @@ import com.kernellabs.kernellabs.infrastructure.WorkRepository; import com.kernellabs.kernellabs.presentation.dto.request.SurveyRequest; import com.kernellabs.kernellabs.presentation.dto.response.SurveyResponse; -import java.util.List; +import java.util.Map; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; @@ -13,65 +13,68 @@ public class WorkService { private final WorkRepository placeRepo; - public SurveyResponse recommend(SurveyRequest req) { - // 1) μ‚¬μš©μž 벑터 계산 + // 1) μ‚¬μš©μž 벑터(u) 계산 (κΈ°μ‘΄ μ½”λ“œ μœ μ§€) double[] u = new double[8]; - // 업무 μœ ν˜• one-hot - u[0] = "A".equals(req.getQ1()) ? 1 : 0; // Dev - u[1] = "B".equals(req.getQ1()) ? 1 : 0; // Doc - u[2] = "C".equals(req.getQ1()) ? 1 : 0; // Meet - u[3] = "D".equals(req.getQ1()) ? 1 : 0; // Call - // μ‹œκ°„ - u[4] = switch(req.getQ2()) { - case "A" -> 0.1; - case "B" -> 0.3; - case "C" -> 0.5; - case "D" -> 0.75; - default -> 1.0; - }; - // 밀집도 - u[5] = switch(req.getQ3()) { - case "A" -> 0.0; - case "B" -> 0.33; - case "C" -> 0.66; - default -> 1.0; - }; - // μ‘°μš©β‡†κ΄€κ΄‘ - u[6] = switch(req.getQ4()) { - case "A" -> 0.0; - case "B" -> 0.5; - default -> 1.0; // Cβ†’1.0, Dβ†’1.0(ν˜Ήμ€ 0으둜 처리) - }; - // 독립 곡간 + u[0] = "A".equals(req.getQ1()) ? 1 : 0; + u[1] = "B".equals(req.getQ1()) ? 1 : 0; + u[2] = "C".equals(req.getQ1()) ? 1 : 0; + u[3] = "D".equals(req.getQ1()) ? 1 : 0; + u[4] = switch(req.getQ2()) { case "A"->0.1; case "B"->0.3; case "C"->0.5; case "D"->0.75; default->1.0; }; + u[5] = switch(req.getQ3()) { case "A"->0.0; case "B"->0.33; case "C"->0.66; default->1.0; }; + u[6] = switch(req.getQ4()) { case "A"->0.0; case "B"->0.5; case "C"->1.0; default->0.0; }; u[7] = "A".equals(req.getQ5()) ? 1 : 0; - // 2) DBμ—μ„œ λͺ¨λ“  μž₯μ†Œ κ°€μ Έμ™€μ„œ 내적 계산 - List places = placeRepo.findAll(); - Work best = null; - double bestScore = Double.NEGATIVE_INFINITY; + // 2) λͺ¨λ“  μž₯μ†Œ μŠ€μ½”μ–΄λ§ β†’ 졜고 1개 pick + Work best = placeRepo.findAll().stream() + .map(p -> Map.entry(p, calcScore(u, p))) + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("μΆ”μ²œν•  μž₯μ†Œκ°€ μ—†μŠ΅λ‹ˆλ‹€")); - for (Work p : places) { - double[] v = { - p.getWorkDev() ? 1:0, - p.getWorkDoc() ? 1:0, - p.getWorkMeet()? 1:0, - p.getWorkCall()?1:0, - p.getHoursNorm(), - p.getCrowdNorm(), - p.getLocationNorm(), - p.getPrivateAvailable()?1:0 - }; - double score = 0; - for (int i = 0; i < u.length; i++) { - score += u[i] * v[i]; - } - if (score > bestScore) { - bestScore = score; - best = p; - } - } return SurveyResponse.from(best); } + /** + * 차이 기반 μœ μ‚¬λ„ 계산 + * - 이진(one-hot) feature: dot-product + * - 연속(norm) feature: 1 - |pref - actual| + * - pref[u6]==0 (μƒκ΄€μ—†μŒ)이면 μœ„μΉ˜ 점수 full(1점) + * - priv bonus: ν•„μš”ν•˜κ³  κ°€λŠ₯ν•˜λ©΄ +1 + */ + private double calcScore(double[] u, Work p) { + double score = 0; + + // 1) 업무 μœ ν˜•(dot) + double[] vType = { + p.getWorkDev()?1:0, + p.getWorkDoc()?1:0, + p.getWorkMeet()?1:0, + p.getWorkCall()?1:0 + }; + for (int i = 0; i < 4; i++) { + score += u[i] * vType[i]; + } + + // 2) μ‹œκ°„ 적합도 + score += 1 - Math.abs(p.getHoursNorm() - u[4]); + + // 3) 뢐빔 적합도 + score += 1 - Math.abs(p.getCrowdNorm() - u[5]); + + // 4) μœ„μΉ˜ 적합도 (μƒκ΄€μ—†μŒ β†’ full 점수) + if (u[6] == 0) { + score += 1; + } else { + score += 1 - Math.abs(p.getLocationNorm() - u[6]); + } + + // 5) 독립 곡간 ν•„μš” λ³΄λ„ˆμŠ€ + if (u[7] == 1 && p.getPrivateAvailable()) { + score += 1; + } + + return score; + } + } From e71c6b9f6f14de704941732cbfa1221907485087 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Fri, 27 Jun 2025 23:31:21 +0900 Subject: [PATCH 14/42] =?UTF-8?q?[feat]=20#18=20-=20CORS=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/global/config/WebConfig.java | 17 +++++++++++++++++ .../controller/VacationController.java | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java new file mode 100644 index 0000000..db3d84d --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java @@ -0,0 +1,17 @@ +package com.kernellabs.kernellabs.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:5173") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } +} \ No newline at end of file diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java index 4118d36..4326ee1 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/VacationController.java @@ -2,6 +2,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @@ -20,7 +21,7 @@ public class VacationController { private final VacationService vacationService; - @GetMapping("/vacations") + @PostMapping("/vacations") public ResponseEntity> getVacationRoute( @RequestBody @Valid RouteRequest routeRequest ) { From 6f6b958bf8e6c42f019189ae07b04b05ee99a03a Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 01:35:48 +0900 Subject: [PATCH 15/42] =?UTF-8?q?[feat]=20#10=20-=20reservation=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20entity=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/kernellabs/domain/Place.java | 57 +++++++++++++++ .../domain/PlaceUnavailableDay.java | 49 +++++++++++++ .../kernellabs/domain/Reservation.java | 69 +++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/domain/Place.java create mode 100644 src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java create mode 100644 src/main/java/com/kernellabs/kernellabs/domain/Reservation.java diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Place.java b/src/main/java/com/kernellabs/kernellabs/domain/Place.java new file mode 100644 index 0000000..85095df --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/Place.java @@ -0,0 +1,57 @@ +package com.kernellabs.kernellabs.domain; + +import com.kernellabs.kernellabs.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import java.time.LocalTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Place extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + @Column(nullable = false) + private String address; + + @Column(nullable = false) + private LocalTime openTime; + + @Column(nullable = false) + private LocalTime closeTime; + + @Column + private String thumbnailUrl; + + @Lob + private String description; + + @Column + private Integer unitPrice; + + @Builder + public Place(String name, String address, LocalTime openTime, LocalTime closeTime, String thumbnailUrl, String description, Integer unitPrice) { + this.name = name; + this.address = address; + this.openTime = openTime; + this.closeTime = closeTime; + this.thumbnailUrl = thumbnailUrl; + this.description = description; + this.unitPrice = unitPrice; + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java b/src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java new file mode 100644 index 0000000..2c89aa5 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java @@ -0,0 +1,49 @@ +package com.kernellabs.kernellabs.domain; + +import com.kernellabs.kernellabs.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDate; +import java.time.LocalTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PlaceUnavailableDay extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; + + @Column(nullable = false) + private LocalDate unavailableDate; + + private String reason; + + // ν•΄λ‹Ή λ‚ μ§œμ— νŠΉλ³„νžˆ μ μš©ν•  운영 μ‹œκ°„ (null이면 μž₯μ†Œμ˜ κΈ°λ³Έ μ‹œκ°„μ„ 따름) + private LocalTime startTimeOverride; + private LocalTime endTimeOverride; + + @Builder + public PlaceUnavailableDay(Place place, LocalDate unavailableDate, String reason, LocalTime startTimeOverride, LocalTime endTimeOverride) { + this.place = place; + this.unavailableDate = unavailableDate; + this.reason = reason; + this.startTimeOverride = startTimeOverride; + this.endTimeOverride = endTimeOverride; + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java b/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java new file mode 100644 index 0000000..7b99624 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java @@ -0,0 +1,69 @@ +package com.kernellabs.kernellabs.domain; + +import com.kernellabs.kernellabs.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Reservation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private LocalDate reservationDate; + + @Column(nullable = false) + private LocalTime startTime; + + @Column(nullable = false) + private LocalTime endTime; + + @Builder + public Reservation(Place place, String password, LocalDate reservationDate, LocalTime startTime, LocalTime endTime) { + this.place = place; + this.password = password; + this.reservationDate = reservationDate; + this.startTime = startTime; + this.endTime = endTime; + } + + public static Reservation create(Place place, String password, LocalDate date, List timeSlots) { + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm"); + LocalTime startTime = LocalTime.parse(timeSlots.get(0), timeFormatter); + LocalTime endTime = LocalTime.parse(timeSlots.get(timeSlots.size() - 1), timeFormatter).plusHours(1); + + return Reservation.builder() + .place(place) + .password(password) + .reservationDate(date) + .startTime(startTime) + .endTime(endTime) + .build(); + } + +} From 48cd7ff95c9bca48e8e94f893781d5bd98faccb7 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 01:36:07 +0900 Subject: [PATCH 16/42] =?UTF-8?q?[feat]=20#10=20-=20respotiroy=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceRepository.java | 10 +++++++ .../PlaceUnavailableDayRepository.java | 12 ++++++++ .../repository/ReservationRepository.java | 28 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java create mode 100644 src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java new file mode 100644 index 0000000..11d0c2e --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java @@ -0,0 +1,10 @@ +package com.kernellabs.kernellabs.infrastructure.repository; + +import com.kernellabs.kernellabs.domain.Place; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PlaceRepository extends JpaRepository { + +} diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java new file mode 100644 index 0000000..bd80b65 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java @@ -0,0 +1,12 @@ +package com.kernellabs.kernellabs.infrastructure.repository; + +import com.kernellabs.kernellabs.domain.PlaceUnavailableDay; +import java.time.LocalDate; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PlaceUnavailableDayRepository extends JpaRepository { + Optional findByPlaceIdAndUnavailableDate(Long placeId, LocalDate date); +} diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java new file mode 100644 index 0000000..039098d --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java @@ -0,0 +1,28 @@ +package com.kernellabs.kernellabs.infrastructure.repository; + +import com.kernellabs.kernellabs.domain.Reservation; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReservationRepository extends JpaRepository { + List findByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter( + Long placeId, + LocalDate date, + LocalTime endTime, + LocalTime startTime + ); + + boolean existsByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter( + Long placeId, + LocalDate date, + LocalTime endTime, + LocalTime startTime + ); +} From 083049f1f2954b3f505f02aba255ccdf07a47469 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 01:36:24 +0900 Subject: [PATCH 17/42] =?UTF-8?q?[feat]=20#10=20-=20request,=20response=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/ReservationRequest.java | 32 ++++++++++++++++++ .../dto/response/ReservationResponse.java | 33 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java new file mode 100644 index 0000000..f10a6f1 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java @@ -0,0 +1,32 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationRequest { + + @NotNull(message = "μž₯μ†Œ IDλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.") + private Long placeId; + + @NotBlank(message = "λΉ„λ°€λ²ˆν˜ΈλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.") + @Pattern(regexp = "\\d{4}", message = "λΉ„λ°€λ²ˆν˜ΈλŠ” 4자리 μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.") + private String password; + + @NotNull(message = "μ˜ˆμ•½ λ‚ μ§œλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.") + @FutureOrPresent(message = "κ³Όκ±° λ‚ μ§œλŠ” μ˜ˆμ•½ν•  수 μ—†μŠ΅λ‹ˆλ‹€.") + private LocalDate reservationDate; + + @NotEmpty(message = "μ˜ˆμ•½ μ‹œκ°„μ€ μ΅œμ†Œ 1개 이상 선택해야 ν•©λ‹ˆλ‹€.") + @Size(max = 3, message = "μ˜ˆμ•½μ€ μ΅œλŒ€ 3μ‹œκ°„κΉŒμ§€ κ°€λŠ₯ν•©λ‹ˆλ‹€.") + private List timeSlots; +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java new file mode 100644 index 0000000..04aa8cc --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java @@ -0,0 +1,33 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import com.kernellabs.kernellabs.domain.Reservation; +import java.time.LocalDate; +import java.time.LocalTime; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ReservationResponse { + + private final Long reservationId; + private final Long placeId; + private final String placeName; + private final LocalDate reservationDate; + private final LocalTime startTime; + private final LocalTime endTime; + + public static ReservationResponse from(Reservation reservation) { + return ReservationResponse.builder() + .reservationId(reservation.getId()) + .placeId(reservation.getPlace().getId()) + .placeName(reservation.getPlace().getName()) + .reservationDate(reservation.getReservationDate()) + .startTime(reservation.getStartTime()) + .endTime(reservation.getEndTime()) + .build(); + } +} From 7c18a7aa3ea41e8b71d6be61a072f17953eecf1e Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 01:36:52 +0900 Subject: [PATCH 18/42] =?UTF-8?q?[feat]=20#10=20-=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReservationService.java | 42 +++++++++ .../validator/ReservationValidator.java | 90 +++++++++++++++++++ .../global/exception/ErrorCode.java | 8 +- .../controller/ReservationController.java | 26 ++++++ 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/ReservationService.java create mode 100644 src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java new file mode 100644 index 0000000..23906dc --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -0,0 +1,42 @@ +package com.kernellabs.kernellabs.application; + +import com.kernellabs.kernellabs.application.validator.ReservationValidator; +import com.kernellabs.kernellabs.domain.Place; +import com.kernellabs.kernellabs.domain.Reservation; +import com.kernellabs.kernellabs.global.exception.CustomException; +import com.kernellabs.kernellabs.global.exception.ErrorCode; +import com.kernellabs.kernellabs.infrastructure.repository.PlaceRepository; +import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; +import jakarta.transaction.Transactional; +import java.time.format.DateTimeFormatter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReservationService { + private final ReservationRepository reservationRepository; + private final PlaceRepository placeRepository; + private final ReservationValidator reservationValidator; + + @Transactional + public ReservationResponse createReservation(ReservationRequest request) { + // 1. μž₯μ†Œ 쑰회 + Place place = placeRepository.findById(request.getPlaceId()) + .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); + + // 2. μœ νš¨μ„± 검증 + reservationValidator.validate(place, request); + + // 3. μ—”ν‹°ν‹° 생성 + Reservation reservation = Reservation.create(place, request.getPassword(), + request.getReservationDate(), request.getTimeSlots()); + + // 4. μ˜ˆμ•½ μ €μž₯ 및 응닡 λ°˜ν™˜ + reservationRepository.save(reservation); + return ReservationResponse.from(reservation); + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java b/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java new file mode 100644 index 0000000..8ba2c90 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java @@ -0,0 +1,90 @@ +package com.kernellabs.kernellabs.application.validator; + +import com.kernellabs.kernellabs.domain.Place; +import com.kernellabs.kernellabs.domain.PlaceUnavailableDay; +import com.kernellabs.kernellabs.global.exception.CustomException; +import com.kernellabs.kernellabs.global.exception.ErrorCode; +import com.kernellabs.kernellabs.infrastructure.repository.PlaceUnavailableDayRepository; +import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ReservationValidator { + + private final ReservationRepository reservationRepository; + private final PlaceUnavailableDayRepository unavailableDayRepository; + private final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + + public void validate(Place place, ReservationRequest request) { + // 1. μ‹œκ°„ 슬둯 자체의 μœ νš¨μ„± 검증 (포맷, 연속성) + validateTimeSlots(request.getTimeSlots()); + + LocalTime requestStartTime = LocalTime.parse(request.getTimeSlots().get(0), TIME_FORMATTER); + LocalTime requestEndTime = LocalTime.parse(request.getTimeSlots().get(request.getTimeSlots().size() - 1), TIME_FORMATTER).plusHours(1); + + // 2. 운영 μ‹œκ°„ λ‚΄μ˜ μš”μ²­μΈμ§€ 검증 + validateAgainstOperatingHours(place, request.getReservationDate(), requestStartTime, requestEndTime); + + // 3. 쀑볡 μ˜ˆμ•½μ΄ μ—†λŠ”μ§€ 검증 + validateNoOverlappingReservations(place.getId(), request.getReservationDate(), requestEndTime, requestStartTime); + } + + // μ‹œκ°„ 슬둯의 포맷과 연속성 검증 + private void validateTimeSlots(List timeSlots) { + if (timeSlots == null || timeSlots.isEmpty()) { + throw new CustomException(ErrorCode.TIME_SLOTS_EMPTY); + } + Collections.sort(timeSlots); + + for (int i = 0; i < timeSlots.size() - 1; i++) { + LocalTime current = LocalTime.parse(timeSlots.get(i), TIME_FORMATTER); + LocalTime next = LocalTime.parse(timeSlots.get(i + 1), TIME_FORMATTER); + if (!current.plusHours(1).equals(next)) { + throw new CustomException(ErrorCode.INVALID_TIME_SLOT_SEQUENCE); + } + } + } + + // ν•΄λ‹Ή λ‚ μ§œμ˜ μ‹€μ œ 운영 μ‹œκ°„ κΈ°μ€€μœΌλ‘œ μš”μ²­μ΄ μœ νš¨ν•œμ§€ 검증 + private void validateAgainstOperatingHours(Place place, LocalDate date, LocalTime requestStartTime, LocalTime requestEndTime) { + OperatingHours operatingHours = getOperatingHoursFor(place, date); + + if (requestStartTime.isBefore(operatingHours.openTime()) || requestEndTime.isAfter(operatingHours.closeTime())) { + throw new CustomException(ErrorCode.INVALID_RESERVATION_TIME); + } + } + + // 쀑볡 μ˜ˆμ•½ 검증 + private void validateNoOverlappingReservations(Long placeId, LocalDate date, LocalTime endTime, LocalTime startTime) { + if (reservationRepository.existsByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter(placeId, date, endTime, startTime)) { + throw new CustomException(ErrorCode.RESERVATION_ALREADY_EXISTS); + } + } + + // νŠΉμ • λ‚ μ§œμ˜ μ‹€μ œ 운영 μ‹œκ°„ 계산 + private OperatingHours getOperatingHoursFor(Place place, LocalDate date) { + Optional unavailableDayOpt = unavailableDayRepository.findByPlaceIdAndUnavailableDate(place.getId(), date); + + if (unavailableDayOpt.isPresent()) { + PlaceUnavailableDay unavailableDay = unavailableDayOpt.get(); + if (unavailableDay.getStartTimeOverride() == null) { + throw new CustomException(ErrorCode.RESERVATION_NOT_POSSIBLE_ON_DAY); + } + return new OperatingHours(unavailableDay.getStartTimeOverride(), unavailableDay.getEndTimeOverride()); + } else { + return new OperatingHours(place.getOpenTime(), place.getCloseTime()); + } + } + + // 운영 μ‹œκ°„μ„ λ‹΄λŠ” κ°„λ‹¨ν•œ λ ˆμ½”λ“œ + private record OperatingHours(LocalTime openTime, LocalTime closeTime) {} +} diff --git a/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java b/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java index e2b1793..58adab4 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java +++ b/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java @@ -28,7 +28,13 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR("9999", "μ„œλ²„ λ‚΄λΆ€ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", HttpStatus.INTERNAL_SERVER_ERROR), EXTERNAL_SERVICE_ERROR("9901", "μ™ΈλΆ€ μ„œλΉ„μŠ€ 연동 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", HttpStatus.INTERNAL_SERVER_ERROR), - ; + // custom error + PLACE_NOT_FOUND("2001", "ν•΄λ‹Ή μž₯μ†Œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", HttpStatus.NOT_FOUND), + TIME_SLOTS_EMPTY("2002", "μ˜ˆμ•½ μ‹œκ°„μ„ μ„ νƒν•΄μ£Όμ„Έμš”.", HttpStatus.BAD_REQUEST ), + INVALID_TIME_SLOT_SEQUENCE("2003", "μ˜ˆμ•½ μ‹œκ°„μ€ μ—°μ†λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST), + RESERVATION_NOT_POSSIBLE_ON_DAY("2004", "μ„ νƒν•˜μ‹  λ‚ μ§œλŠ” μ˜ˆμ•½μ΄ λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.", HttpStatus.CONFLICT), + INVALID_RESERVATION_TIME("2005", "μ˜ˆμ•½ μš”μ²­ μ‹œκ°„μ΄ 운영 μ‹œκ°„ λ²”μœ„λ₯Ό λ²—μ–΄λ‚©λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST), + RESERVATION_ALREADY_EXISTS("2006", "ν•΄λ‹Ή μ‹œκ°„μ— 이미 μ˜ˆμ•½μ΄ μ‘΄μž¬ν•©λ‹ˆλ‹€.", HttpStatus.CONFLICT); private final HttpStatus status; private final String code; diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java new file mode 100644 index 0000000..d317ea9 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -0,0 +1,26 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import com.kernellabs.kernellabs.application.ReservationService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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 ReservationController { + + private final ReservationService reservationService; + + @PostMapping("/reservations") + public ResponseEntity createReservation(@RequestBody @Valid ReservationRequest request) { + ReservationResponse response = reservationService.createReservation(request); + return ResponseEntity.ok(ApiResponse.success(response)); + } + +} From bd8582afa675e1e2d7c67a3477232bc4396de344 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Sat, 28 Jun 2025 02:05:12 +0900 Subject: [PATCH 19/42] =?UTF-8?q?[feat]=20CORS=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20front=20=EC=A3=BC=EC=86=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kernellabs/kernellabs/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java index db3d84d..223ab6a 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java +++ b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java @@ -9,7 +9,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:5173") + .allowedOrigins("http://localhost:5173", "https://kernel-labs-fee.vercel.app/") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); From 88d5c0d3481cc8070920950de06fc9fac698c8ed Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 02:14:27 +0900 Subject: [PATCH 20/42] =?UTF-8?q?[feat]=20#10=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20request=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/ReservationUpdateRequest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java new file mode 100644 index 0000000..c7a4bc2 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java @@ -0,0 +1,30 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationUpdateRequest { + + @NotBlank(message = "λΉ„λ°€λ²ˆν˜ΈλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.") + @Pattern(regexp = "\\d{4}", message = "λΉ„λ°€λ²ˆν˜ΈλŠ” 4자리 μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.") + private String password; + + @NotNull(message = "λ³€κ²½ν•  λ‚ μ§œλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.") + @FutureOrPresent(message = "κ³Όκ±° λ‚ μ§œλ‘œλŠ” λ³€κ²½ν•  수 μ—†μŠ΅λ‹ˆλ‹€.") + private LocalDate newReservationDate; + + @NotEmpty(message = "λ³€κ²½ν•  μ˜ˆμ•½ μ‹œκ°„μ€ μ΅œμ†Œ 1개 이상 선택해야 ν•©λ‹ˆλ‹€.") + @Size(max = 3, message = "μ˜ˆμ•½μ€ μ΅œλŒ€ 3μ‹œκ°„κΉŒμ§€ κ°€λŠ₯ν•©λ‹ˆλ‹€.") + private List newTimeSlots; + +} From d00f11fbc6665180642a3461cfd8a41a07dd4ac5 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 02:15:29 +0900 Subject: [PATCH 21/42] =?UTF-8?q?[feat]=20#10=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kernellabs/kernellabs/domain/Reservation.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java b/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java index 7b99624..3b49e4b 100644 --- a/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java +++ b/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java @@ -66,4 +66,10 @@ public static Reservation create(Place place, String password, LocalDate date, L .build(); } + public void updateTimes(LocalDate newDate, LocalTime newStartTime, LocalTime newEndTime) { + this.reservationDate = newDate; + this.startTime = newStartTime; + this.endTime = newEndTime; + } + } From 8318e527420ecf098600b17321976a3d9c12d7d8 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 02:15:50 +0900 Subject: [PATCH 22/42] =?UTF-8?q?[feat]=20#10=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReservationService.java | 43 ++++++++++++++++++- .../validator/ReservationValidator.java | 29 ++++++++++++- .../com/kernellabs/kernellabs/domain/.gitkeep | 0 .../global/exception/ErrorCode.java | 3 +- .../repository/ReservationRepository.java | 6 ++- .../controller/ReservationController.java | 15 ++++++- 6 files changed, 89 insertions(+), 7 deletions(-) delete mode 100644 src/main/java/com/kernellabs/kernellabs/domain/.gitkeep diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java index 23906dc..a476db9 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -7,10 +7,18 @@ import com.kernellabs.kernellabs.global.exception.ErrorCode; import com.kernellabs.kernellabs.infrastructure.repository.PlaceRepository; import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationDeleteRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import jakarta.transaction.Transactional; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -28,7 +36,7 @@ public ReservationResponse createReservation(ReservationRequest request) { .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); // 2. μœ νš¨μ„± 검증 - reservationValidator.validate(place, request); + reservationValidator.validateForCreate(place, request); // 3. μ—”ν‹°ν‹° 생성 Reservation reservation = Reservation.create(place, request.getPassword(), @@ -39,4 +47,37 @@ public ReservationResponse createReservation(ReservationRequest request) { return ReservationResponse.from(reservation); } + @Transactional + public ReservationResponse updateReservation(Long reservationId, ReservationUpdateRequest request) { + // 1. μ˜ˆμ•½ 쑰회 및 λΉ„λ°€λ²ˆν˜Έ 확인 + Reservation reservation = findReservationById(reservationId); + validatePassword(request.getPassword(), reservation.getPassword()); + + // 2. λ³€κ²½ μš”μ²­ μœ νš¨μ„± 검사 + reservationValidator.validateForUpdate(reservation, request); + + // 3. μ—”ν‹°ν‹° μƒνƒœ λ³€κ²½ + reservation.updateTimes(request.getNewReservationDate(), parseStartTime(request.getNewTimeSlots()), parseEndTime(request.getNewTimeSlots())); + return ReservationResponse.from(reservation); + } + + private LocalTime parseStartTime(List timeSlots) { + return LocalTime.parse(timeSlots.get(0), DateTimeFormatter.ofPattern("HH:mm")); + } + + private LocalTime parseEndTime(List timeSlots) { + return LocalTime.parse(timeSlots.get(timeSlots.size() - 1), DateTimeFormatter.ofPattern("HH:mm")).plusHours(1); + } + + private void validatePassword(String rawPassword, String storedPassword) { + if (!rawPassword.equals(storedPassword)) { + throw new CustomException(ErrorCode.INVALID_PASSWORD); + } + } + + private Reservation findReservationById(Long reservationId) { + return reservationRepository.findById(reservationId) + .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); + } + } diff --git a/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java b/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java index 8ba2c90..1cad215 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java +++ b/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java @@ -2,11 +2,13 @@ import com.kernellabs.kernellabs.domain.Place; import com.kernellabs.kernellabs.domain.PlaceUnavailableDay; +import com.kernellabs.kernellabs.domain.Reservation; import com.kernellabs.kernellabs.global.exception.CustomException; import com.kernellabs.kernellabs.global.exception.ErrorCode; import com.kernellabs.kernellabs.infrastructure.repository.PlaceUnavailableDayRepository; import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -24,7 +26,7 @@ public class ReservationValidator { private final PlaceUnavailableDayRepository unavailableDayRepository; private final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); - public void validate(Place place, ReservationRequest request) { + public void validateForCreate(Place place, ReservationRequest request) { // 1. μ‹œκ°„ 슬둯 자체의 μœ νš¨μ„± 검증 (포맷, 연속성) validateTimeSlots(request.getTimeSlots()); @@ -38,6 +40,18 @@ public void validate(Place place, ReservationRequest request) { validateNoOverlappingReservations(place.getId(), request.getReservationDate(), requestEndTime, requestStartTime); } + public void validateForUpdate(Reservation reservation, ReservationUpdateRequest request) { + LocalDate newDate = request.getNewReservationDate(); + List newTimeSlots = request.getNewTimeSlots(); + validateTimeSlots(newTimeSlots); + + LocalTime newStartTime = LocalTime.parse(request.getNewTimeSlots().get(0), TIME_FORMATTER); + LocalTime newEndTime = LocalTime.parse(request.getNewTimeSlots().get(request.getNewTimeSlots().size() - 1), TIME_FORMATTER).plusHours(1); + + validateAgainstOperatingHours(reservation.getPlace(), newDate, newStartTime, newEndTime); + // 자기 μžμ‹  μ œμ™Έν•˜κ³  쀑볡 검사 + validateNoOverlappingForUpdate(reservation, newDate, newEndTime, newStartTime); } + // μ‹œκ°„ 슬둯의 포맷과 연속성 검증 private void validateTimeSlots(List timeSlots) { if (timeSlots == null || timeSlots.isEmpty()) { @@ -87,4 +101,17 @@ private OperatingHours getOperatingHoursFor(Place place, LocalDate date) { // 운영 μ‹œκ°„μ„ λ‹΄λŠ” κ°„λ‹¨ν•œ λ ˆμ½”λ“œ private record OperatingHours(LocalTime openTime, LocalTime closeTime) {} + + // 자기 μžμ‹ μ„ μ œμ™Έν•˜κ³  쀑볡 μ˜ˆμ•½μ„ ν™•μΈν•˜λŠ” λ©”μ„œλ“œ + private void validateNoOverlappingForUpdate(Reservation reservation, LocalDate newDate, LocalTime newEndTime, LocalTime newStartTime) { + if (reservationRepository.existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAfter( + reservation.getPlace().getId(), + newDate, + reservation.getId(), + newEndTime, + newStartTime + )) { + throw new CustomException(ErrorCode.RESERVATION_ALREADY_EXISTS); + } + } } diff --git a/src/main/java/com/kernellabs/kernellabs/domain/.gitkeep b/src/main/java/com/kernellabs/kernellabs/domain/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java b/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java index 58adab4..faff81a 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java +++ b/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java @@ -34,7 +34,8 @@ public enum ErrorCode { INVALID_TIME_SLOT_SEQUENCE("2003", "μ˜ˆμ•½ μ‹œκ°„μ€ μ—°μ†λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST), RESERVATION_NOT_POSSIBLE_ON_DAY("2004", "μ„ νƒν•˜μ‹  λ‚ μ§œλŠ” μ˜ˆμ•½μ΄ λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.", HttpStatus.CONFLICT), INVALID_RESERVATION_TIME("2005", "μ˜ˆμ•½ μš”μ²­ μ‹œκ°„μ΄ 운영 μ‹œκ°„ λ²”μœ„λ₯Ό λ²—μ–΄λ‚©λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST), - RESERVATION_ALREADY_EXISTS("2006", "ν•΄λ‹Ή μ‹œκ°„μ— 이미 μ˜ˆμ•½μ΄ μ‘΄μž¬ν•©λ‹ˆλ‹€.", HttpStatus.CONFLICT); + RESERVATION_ALREADY_EXISTS("2006", "ν•΄λ‹Ή μ‹œκ°„μ— 이미 μ˜ˆμ•½μ΄ μ‘΄μž¬ν•©λ‹ˆλ‹€.", HttpStatus.CONFLICT), + INVALID_PASSWORD("2007", "λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.", HttpStatus.FORBIDDEN); private final HttpStatus status; private final String code; diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java index 039098d..7606690 100644 --- a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java @@ -12,16 +12,18 @@ @Repository public interface ReservationRepository extends JpaRepository { - List findByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter( + boolean existsByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter( Long placeId, LocalDate date, LocalTime endTime, LocalTime startTime ); - boolean existsByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter( + // μ˜ˆμ•½ λ³€κ²½ μ‹œ, κ²ΉμΉ˜λŠ” μ˜ˆμ•½μ΄ μžˆλŠ”μ§€ 확인 (자기 μžμ‹  μ œμ™Έ) + boolean existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAfter( Long placeId, LocalDate date, + Long reservationId, LocalTime endTime, LocalTime startTime ); diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java index d317ea9..18d403c 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -3,24 +3,35 @@ import com.kernellabs.kernellabs.application.ReservationService; import com.kernellabs.kernellabs.global.common.ApiResponse; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController +@RequestMapping("/reservations") @RequiredArgsConstructor public class ReservationController { private final ReservationService reservationService; - @PostMapping("/reservations") - public ResponseEntity createReservation(@RequestBody @Valid ReservationRequest request) { + @PostMapping("") + public ResponseEntity> createReservation(@RequestBody @Valid ReservationRequest request) { ReservationResponse response = reservationService.createReservation(request); return ResponseEntity.ok(ApiResponse.success(response)); } + @PatchMapping("/{reservationId}") + public ResponseEntity> updateReservation(@PathVariable Long reservationId, + @Valid @RequestBody ReservationUpdateRequest request) { + ReservationResponse response = reservationService.updateReservation(reservationId, request); + return ResponseEntity.ok(ApiResponse.success(response)); + } } From d6c9003affe85b9d9d8d9fe01ae4f927e5055272 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 02:18:37 +0900 Subject: [PATCH 23/42] =?UTF-8?q?[feat]=20#10=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReservationService.java | 10 ++++++++++ .../controller/ReservationController.java | 9 +++++++++ .../dto/request/ReservationDeleteRequest.java | 15 +++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java index a476db9..ca4da13 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -61,6 +61,16 @@ public ReservationResponse updateReservation(Long reservationId, ReservationUpda return ReservationResponse.from(reservation); } + @Transactional + public void deleteReservation(Long reservationId, ReservationDeleteRequest request) { + // 1. μ˜ˆμ•½ 쑰회 및 λΉ„λ°€λ²ˆν˜Έ 확인 + Reservation reservation = findReservationById(reservationId); + validatePassword(request.getPassword(), reservation.getPassword()); + + // 2. μ˜ˆμ•½ μ‚­μ œ + reservationRepository.delete(reservation); + } + private LocalTime parseStartTime(List timeSlots) { return LocalTime.parse(timeSlots.get(0), DateTimeFormatter.ofPattern("HH:mm")); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java index 18d403c..f3d321f 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -2,12 +2,14 @@ import com.kernellabs.kernellabs.application.ReservationService; import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationDeleteRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -34,4 +36,11 @@ public ResponseEntity> updateReservation(@PathV ReservationResponse response = reservationService.updateReservation(reservationId, request); return ResponseEntity.ok(ApiResponse.success(response)); } + + @DeleteMapping("/{reservationId}") + public ResponseEntity deleteReservation(@PathVariable Long reservationId, + @Valid @RequestBody ReservationDeleteRequest request) { + reservationService.deleteReservation(reservationId, request); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java new file mode 100644 index 0000000..6601a9c --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java @@ -0,0 +1,15 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationDeleteRequest { + + @NotBlank(message = "λΉ„λ°€λ²ˆν˜ΈλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.") + @Pattern(regexp = "\\d{4}", message = "λΉ„λ°€λ²ˆν˜ΈλŠ” 4자리 μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.") + private String password; +} \ No newline at end of file From 275506b4fad3f4d21a743162d667ed6546a3892e Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 02:25:02 +0900 Subject: [PATCH 24/42] =?UTF-8?q?[feat]=20#10=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/ReservationService.java | 12 +++++++----- .../controller/ReservationController.java | 7 +++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java index ca4da13..d4fad37 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -11,17 +11,14 @@ import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; -import jakarta.transaction.Transactional; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +@Transactional(readOnly = true) @Service @RequiredArgsConstructor public class ReservationService { @@ -47,6 +44,11 @@ public ReservationResponse createReservation(ReservationRequest request) { return ReservationResponse.from(reservation); } + public ReservationResponse getReservation(Long reservationId) { + Reservation reservation = findReservationById(reservationId); + return ReservationResponse.from(reservation); + } + @Transactional public ReservationResponse updateReservation(Long reservationId, ReservationUpdateRequest request) { // 1. μ˜ˆμ•½ 쑰회 및 λΉ„λ°€λ²ˆν˜Έ 확인 diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java index f3d321f..51445d0 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -30,6 +31,12 @@ public ResponseEntity> createReservation(@Reque return ResponseEntity.ok(ApiResponse.success(response)); } + @GetMapping("/{reservationId}") + public ResponseEntity> getReservation(@PathVariable Long reservationId) { + ReservationResponse response = reservationService.getReservation(reservationId); + return ResponseEntity.ok(ApiResponse.success(response)); + } + @PatchMapping("/{reservationId}") public ResponseEntity> updateReservation(@PathVariable Long reservationId, @Valid @RequestBody ReservationUpdateRequest request) { From ea57c94670216f718cfb477cc769af40e5e2ac3c Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 06:51:31 +0900 Subject: [PATCH 25/42] =?UTF-8?q?[feat]=20#22=20-=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20?= =?UTF-8?q?response=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PlaceListResponse.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceListResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceListResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceListResponse.java new file mode 100644 index 0000000..d6a9026 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceListResponse.java @@ -0,0 +1,25 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import com.kernellabs.kernellabs.domain.Place; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PlaceListResponse { + + private final Long id; + private final String thumbnailUrl; + private final String name; + private final String address; + + public static PlaceListResponse from(Place place) { + return PlaceListResponse.builder() + .id(place.getId()) + .thumbnailUrl(place.getThumbnailUrl()) + .name(place.getName()) + .address(place.getAddress()) + .build(); + } + +} From e017d825c9af45b0223248d59bb32e63dc907c0a Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 06:51:42 +0900 Subject: [PATCH 26/42] =?UTF-8?q?[feat]=20#22=20-=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EA=B0=9C=EB=B3=84=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20response?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PlaceViewResponse.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java new file mode 100644 index 0000000..c8d04e5 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java @@ -0,0 +1,34 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import com.kernellabs.kernellabs.domain.Place; +import java.time.LocalTime; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PlaceViewResponse { + + private final Long id; + private final String thumbnailUrl; + private final String name; + private final String address; + private final LocalTime openTime; + private final LocalTime closeTime; + private final Integer unitPrice; + private final String description; + + public static PlaceViewResponse from(Place place) { + return PlaceViewResponse.builder() + .id(place.getId()) + .thumbnailUrl(place.getThumbnailUrl()) + .name(place.getName()) + .address(place.getAddress()) + .openTime(place.getOpenTime()) + .closeTime(place.getCloseTime()) + .unitPrice(place.getUnitPrice()) + .description(place.getDescription()) + .build(); + } + +} From 5a655825dc28966185db83832cb1112c177659e4 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 06:51:53 +0900 Subject: [PATCH 27/42] =?UTF-8?q?[feat]=20#22=20-=20=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/PlaceService.java | 34 +++++++++++++++++++ .../controller/PlaceController.java | 33 ++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/PlaceService.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java new file mode 100644 index 0000000..936515a --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java @@ -0,0 +1,34 @@ +package com.kernellabs.kernellabs.application; + +import com.kernellabs.kernellabs.domain.Place; +import com.kernellabs.kernellabs.global.exception.CustomException; +import com.kernellabs.kernellabs.global.exception.ErrorCode; +import com.kernellabs.kernellabs.infrastructure.repository.PlaceRepository; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceViewResponse; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PlaceService { + + private final PlaceRepository placeRepository; + + public List getAllPlace() { + return placeRepository.findAll().stream() + .map(PlaceListResponse::from) + .collect(Collectors.toList()); + } + + public PlaceViewResponse getPlaceDetail(Long placeId) { + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); + + return PlaceViewResponse.from(place); + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java new file mode 100644 index 0000000..e25fcc3 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java @@ -0,0 +1,33 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import com.kernellabs.kernellabs.application.PlaceService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceViewResponse; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/places") +@RequiredArgsConstructor +public class PlaceController { + + private final PlaceService placeService; + + @GetMapping("") + public ResponseEntity getAllPlaces() { + List response = placeService.getAllPlace(); + return ResponseEntity.ok(ApiResponse.success(response)); + } + + @GetMapping("/{placeId}") + public ResponseEntity> getPlace(@PathVariable Long placeId) { + PlaceViewResponse response = placeService.getPlaceDetail(placeId); + return ResponseEntity.ok(ApiResponse.success(response)); + } +} From 0dc6340323085e6adcf9238fdbce6a8d2b12da86 Mon Sep 17 00:00:00 2001 From: HoyiTT Date: Sat, 28 Jun 2025 07:24:08 +0900 Subject: [PATCH 28/42] =?UTF-8?q?[feat]=20#13=20=EC=9B=8C=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=9E=A5=EC=86=8C=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/kernellabs/kernellabs/domain/Work.java | 1 + .../kernellabs/presentation/dto/response/SurveyResponse.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Work.java b/src/main/java/com/kernellabs/kernellabs/domain/Work.java index b06960f..70a5e28 100644 --- a/src/main/java/com/kernellabs/kernellabs/domain/Work.java +++ b/src/main/java/com/kernellabs/kernellabs/domain/Work.java @@ -25,6 +25,7 @@ public class Work { private Long id; private String name; // μž₯μ†Œλͺ… + private String imgUrl; // 이미지 μ£Όμ†Œ @Column(columnDefinition = "TEXT") private String description; // μ„€λͺ… diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java index 518aea3..04fbfd2 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/SurveyResponse.java @@ -11,13 +11,15 @@ public class SurveyResponse { private String description; // μ„€λͺ… private String type; // 곡유 μ˜€ν”ΌμŠ€/카페/μŠ€ν„°λ””μΉ΄νŽ˜ λ“± private String address; // μ£Όμ†Œ + private String imgUrl; public static SurveyResponse from(Work work) { return new SurveyResponse( work.getName(), work.getDescription(), work.getType(), - work.getAddress() + work.getAddress(), + work.getImgUrl() ); } } From c236fc8b033bc36ec2284d45f74e14285371a1cb Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Sat, 28 Jun 2025 10:13:03 +0900 Subject: [PATCH 29/42] =?UTF-8?q?[feat]=20#25=20chatbot=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ChatbotService.java | 37 +++++++++++++++++++ .../controller/ChatbotController.java | 33 +++++++++++++++++ .../dto/request/ChatAnswerRequest.java | 12 ++++++ .../dto/response/ChatAnswerResponse.java | 10 +++++ 4 files changed, 92 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/ChatbotService.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/ChatbotController.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ChatAnswerRequest.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ChatAnswerResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/ChatbotService.java b/src/main/java/com/kernellabs/kernellabs/application/ChatbotService.java new file mode 100644 index 0000000..7f3b6b9 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/ChatbotService.java @@ -0,0 +1,37 @@ +package com.kernellabs.kernellabs.application; + +import org.springframework.stereotype.Service; + +import com.kernellabs.kernellabs.infrastructure.external.GeminiApiClient; +import com.kernellabs.kernellabs.presentation.dto.request.ChatAnswerRequest; +import com.kernellabs.kernellabs.presentation.dto.response.ChatAnswerResponse; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ChatbotService { + + private final GeminiApiClient geminiApiClient; + + public ChatAnswerResponse chat(ChatAnswerRequest chatAnswerRequest) { + String prompt = String.format( + "당신은 μ˜μ„±κ΅°μ— νŠΉν™”λœ 정보 제곡 μ±—λ΄‡μž…λ‹ˆλ‹€.\n" + + "λͺ¨λ“  닡변은 μ˜μ„±κ΅°μ— λŒ€ν•œ λ‚΄μš©λ§Œμ„ 포함해야 ν•©λ‹ˆλ‹€. λ‹€λ₯Έ μ§€μ—­ μ •λ³΄λ‚˜ 일반적인 λ‚΄μš©μ€ μ–ΈκΈ‰ν•˜μ§€ λ§ˆμ„Έμš”.\n" + + "μ‚¬μš©μžμ˜ 질문이 μ˜μ„±κ΅°κ³Ό 직접적인 관련이 μ—†κ±°λ‚˜ μ˜μ„±κ΅°μ— λŒ€ν•œ μ •λ³΄λ‘œ λ‹΅λ³€ν•  수 μ—†λŠ” 경우, 'μ£„μ†‘ν•©λ‹ˆλ‹€. μ €λŠ” μ˜μ„±κ΅°μ— λŒ€ν•œ μ •λ³΄λ§Œμ„ μ œκ³΅ν•©λ‹ˆλ‹€.' 와 같이 λ‹΅λ³€ λ²”μœ„λ₯Ό λͺ…ν™•νžˆ μ•Œλ¦¬κ³  μΆ”κ°€ μ§ˆλ¬Έμ„ μœ λ„ν•˜μ„Έμš”.\n\n" + + "μ˜μ„±κ΅°μ˜ 지리, 역사, λ¬Έν™”, νŠΉμ‚°λ¬Ό, μ£Όμš” μ‚°μ—…(특히 농업), κ΄€κ΄‘μ§€, μΆ•μ œ, 인ꡬ ν˜„ν™©, κΈ°ν›„, μ •μ±…(μ›ŒμΌ€μ΄μ…˜, κ·€λ†κ·€μ΄Œ λ“±), ꡐ톡 λ“±μ˜ 정보에 쀑점을 λ‘‘λ‹ˆλ‹€.\n" + + "특히, μ›ŒμΌ€μ΄μ…˜, 슀마트 농업, μƒν™œμΈκ΅¬ μœ μž…κ³Ό κ΄€λ ¨λœ μ˜μ„±κ΅°μ˜ 상세 정보에 μš°μ„ μˆœμœ„λ₯Ό 두고 λ‹΅λ³€ν•΄ μ£Όμ„Έμš”.\n" + + "질문이 λͺ¨ν˜Έν•  경우, μ˜μ„±κ΅°κ³Ό κ΄€λ ¨λœ κ°€μž₯ κ΄€λ ¨μ„± 높은 정보λ₯Ό μš°μ„ μ μœΌλ‘œ μ œκ³΅ν•΄ μ£Όμ„Έμš”.\n\n" + + "닡변은 μ •ν™•ν•˜κ³  사싀에 κΈ°λ°˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.\n" + + "μΉœμ ˆν•˜κ³  λͺ…ν™•ν•œ μ–΄μ‘°λ‘œ λ‹΅λ³€ν•΄ μ£Όμ„Έμš”.\n" + + "ν•„μš”μ‹œ 정보λ₯Ό λͺ©λ‘μ΄λ‚˜ κ°„κ²°ν•œ 문단 ν˜•μ‹μœΌλ‘œ μ •λ¦¬ν•˜μ—¬ 가독성을 λ†’μ—¬μ£Όμ„Έμš”.\n" + + "μ‚¬μš©μžκ°€ 더 κΉŠμ€ 정보λ₯Ό 원할 경우, μΆ”κ°€ μ§ˆλ¬Έμ„ μœ λ„ν•˜λŠ” ν˜•νƒœλ‘œ λŒ€ν™”λ₯Ό μ΄λŒμ–΄ λ‚˜κ°ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.\n\n" + + "μ‚¬μš©μžμ˜ 질문: %s", + chatAnswerRequest.getQuestion()); + + String answer = geminiApiClient.askGemini(prompt); + return ChatAnswerResponse.builder().answer(answer).build(); + } + + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ChatbotController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ChatbotController.java new file mode 100644 index 0000000..4f015a2 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ChatbotController.java @@ -0,0 +1,33 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.kernellabs.kernellabs.application.ChatbotService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.request.ChatAnswerRequest; +import com.kernellabs.kernellabs.presentation.dto.response.ChatAnswerResponse; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/chats") +public class ChatbotController { + + private final ChatbotService chatbotService; + + @PostMapping("") + public ResponseEntity> chat( + @RequestBody ChatAnswerRequest chatAnswerRequest + ) { + ChatAnswerResponse chatAnswerResponse = chatbotService.chat(chatAnswerRequest); + return ResponseEntity.ok(ApiResponse.success(chatAnswerResponse )); + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ChatAnswerRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ChatAnswerRequest.java new file mode 100644 index 0000000..85121cc --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ChatAnswerRequest.java @@ -0,0 +1,12 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ChatAnswerRequest { + @NotNull + private String question; +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ChatAnswerResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ChatAnswerResponse.java new file mode 100644 index 0000000..fb717fa --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ChatAnswerResponse.java @@ -0,0 +1,10 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ChatAnswerResponse { + private String answer; +} From 25f68fb2882aa5872160f1f32816e9c0dacdd3c1 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Sat, 28 Jun 2025 11:02:06 +0900 Subject: [PATCH 30/42] =?UTF-8?q?[feat]=20#27=20swagger=20https=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/global/config/SwaggerConfig.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/SwaggerConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/SwaggerConfig.java index 93eb7b6..7ea1031 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/config/SwaggerConfig.java +++ b/src/main/java/com/kernellabs/kernellabs/global/config/SwaggerConfig.java @@ -6,15 +6,20 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; @Configuration public class SwaggerConfig { @Bean public OpenAPI openAPI(){ + Server server = new Server(); + server.setDescription("Production Server"); + return new OpenAPI() .components(new Components()) - .info(apiInfo()); + .info(apiInfo()) + .addServersItem(server); } private Info apiInfo(){ @@ -23,4 +28,4 @@ private Info apiInfo(){ .description("Improfessor API Documentation") .version("1.0"); } -} +} \ No newline at end of file From bd6c4e0d486c06023d4bfa9f3d44086022116000 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Sat, 28 Jun 2025 11:08:36 +0900 Subject: [PATCH 31/42] =?UTF-8?q?[feat]=20#27=20domain=20cors=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kernellabs/kernellabs/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java index 223ab6a..e800d5f 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java +++ b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java @@ -9,7 +9,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:5173", "https://kernel-labs-fee.vercel.app/") + .allowedOrigins("http://localhost:5173", "https://kernel-labs-fee.vercel.app/", "https://uscode.porogramr.site/") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); From 9b1b548f2c941e4f15a8532fb0e353a485d203e9 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 11:38:23 +0900 Subject: [PATCH 32/42] =?UTF-8?q?[feat]=20#30=20-=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=20=EC=B6=94=EA=B0=80=ED=95=9C=20response=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PlaceDetailResponse.java | 35 +++++++++++++++++++ .../dto/response/TimeSlotResponse.java | 13 +++++++ 2 files changed, 48 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceDetailResponse.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceDetailResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceDetailResponse.java new file mode 100644 index 0000000..2000b00 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceDetailResponse.java @@ -0,0 +1,35 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import com.kernellabs.kernellabs.domain.Place; +import java.time.LocalTime; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PlaceDetailResponse { + private Long id; + private String name; + private String address; + private String thumbnailUrl; + private String description; + private LocalTime openTime; + private LocalTime closeTime; + private Integer unitPrice; + private List timeSlots; // μ‹œκ°„ν‘œ 정보 + + public static PlaceDetailResponse of(Place place, List timeSlots) { + return PlaceDetailResponse.builder() + .id(place.getId()) + .name(place.getName()) + .address(place.getAddress()) + .thumbnailUrl(place.getThumbnailUrl()) + .description(place.getDescription()) + .openTime(place.getOpenTime()) + .closeTime(place.getCloseTime()) + .unitPrice(place.getUnitPrice()) + .timeSlots(timeSlots) + .build(); + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java new file mode 100644 index 0000000..764ac2f --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java @@ -0,0 +1,13 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TimeSlotResponse { + + private String time; + private boolean isAvailable; + +} From 9e2c9172714c676df0338c8b5e9eb00176d72ff4 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 11:39:13 +0900 Subject: [PATCH 33/42] =?UTF-8?q?[chore]=20#30=20-=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20response=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PlaceViewResponse.java | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java deleted file mode 100644 index c8d04e5..0000000 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PlaceViewResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.kernellabs.kernellabs.presentation.dto.response; - -import com.kernellabs.kernellabs.domain.Place; -import java.time.LocalTime; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class PlaceViewResponse { - - private final Long id; - private final String thumbnailUrl; - private final String name; - private final String address; - private final LocalTime openTime; - private final LocalTime closeTime; - private final Integer unitPrice; - private final String description; - - public static PlaceViewResponse from(Place place) { - return PlaceViewResponse.builder() - .id(place.getId()) - .thumbnailUrl(place.getThumbnailUrl()) - .name(place.getName()) - .address(place.getAddress()) - .openTime(place.getOpenTime()) - .closeTime(place.getCloseTime()) - .unitPrice(place.getUnitPrice()) - .description(place.getDescription()) - .build(); - } - -} From 75422b8e11fba44d2ece6ed5abde991f43bfe84f Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 11:39:30 +0900 Subject: [PATCH 34/42] =?UTF-8?q?[feat]=20#30=20-=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80=20=ED=8F=AC=ED=95=A8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/PlaceService.java | 52 +++++++++++++++++-- .../repository/ReservationRepository.java | 2 + .../controller/PlaceController.java | 14 +++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java index 936515a..d75ce64 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java @@ -1,12 +1,19 @@ package com.kernellabs.kernellabs.application; import com.kernellabs.kernellabs.domain.Place; +import com.kernellabs.kernellabs.domain.Reservation; import com.kernellabs.kernellabs.global.exception.CustomException; import com.kernellabs.kernellabs.global.exception.ErrorCode; import com.kernellabs.kernellabs.infrastructure.repository.PlaceRepository; +import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceDetailResponse; import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; -import com.kernellabs.kernellabs.presentation.dto.response.PlaceViewResponse; +import com.kernellabs.kernellabs.presentation.dto.response.TimeSlotResponse; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,6 +25,7 @@ public class PlaceService { private final PlaceRepository placeRepository; + private final ReservationRepository reservationRepository; public List getAllPlace() { return placeRepository.findAll().stream() @@ -25,10 +33,48 @@ public List getAllPlace() { .collect(Collectors.toList()); } - public PlaceViewResponse getPlaceDetail(Long placeId) { + public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date) { + // 1. μž₯μ†Œ 정보 쑰회 Place place = placeRepository.findById(placeId) .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); - return PlaceViewResponse.from(place); + // 2. ν•΄λ‹Ή λ‚ μ§œ μ‹€μ œ 운영 μ‹œκ°„ 확인 + LocalTime openTime = place.getOpenTime(); + LocalTime closeTime = place.getCloseTime(); + + // 3. ν•΄λ‹Ή λ‚ μ§œμ— 이미 μ˜ˆμ•½λœ μ‹œκ°„ λͺ©λ‘ 쑰회 + List reservations = reservationRepository.findByPlaceIdAndReservationDate(placeId, date); + Set reservedSlots = getReservedSlots(reservations); + + // 4. 전체 μ‹œκ°„ 슬둯 생성 및 μ˜ˆμ•½ κ°€λŠ₯ μ—¬λΆ€ νŒλ‹¨ + List timeSlots = generateTimeSlots(openTime, closeTime, reservedSlots); + + // 5. μ΅œμ’… 응닡 DTO 생성 및 λ°˜ν™˜ + return PlaceDetailResponse.of(place, timeSlots); + } + + private List generateTimeSlots(LocalTime openTime, LocalTime closeTime, Set reservedSlots) { + List slots = new ArrayList<>(); + LocalTime currentTime = openTime; + while (!currentTime.isAfter(closeTime.minusHours(1))) { + boolean isAvailable = !reservedSlots.contains(currentTime); + slots.add(new TimeSlotResponse(currentTime.toString(), isAvailable)); + currentTime = currentTime.plusHours(1); + } + return slots; + } + + private Set getReservedSlots(List reservations) { + return reservations.stream() + .flatMap(reservation -> { + List slots = new ArrayList<>(); + LocalTime current = reservation.getStartTime(); + while (current.isBefore(reservation.getEndTime())) { + slots.add(current); + current = current.plusHours(1); + } + return slots.stream(); + }) + .collect(Collectors.toSet()); } } diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java index 7606690..a1fba65 100644 --- a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java @@ -27,4 +27,6 @@ boolean existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAft LocalTime endTime, LocalTime startTime ); + + List findByPlaceIdAndReservationDate(Long placeId, LocalDate date); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java index e25fcc3..b83475b 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java @@ -2,14 +2,18 @@ import com.kernellabs.kernellabs.application.PlaceService; import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.response.PlaceDetailResponse; import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; -import com.kernellabs.kernellabs.presentation.dto.response.PlaceViewResponse; +import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -26,8 +30,12 @@ public ResponseEntity getAllPlaces() { } @GetMapping("/{placeId}") - public ResponseEntity> getPlace(@PathVariable Long placeId) { - PlaceViewResponse response = placeService.getPlaceDetail(placeId); + public ResponseEntity> getPlace(@PathVariable Long placeId, + @RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate date) { + // λ‚ μ§œ νŒŒλΌλ―Έν„°κ°€ μ—†μœΌλ©΄ 였늘 λ‚ μ§œ κΈ°λ³Έκ°’ + LocalDate targetDate = (date == null) ? LocalDate.now() : date; + + PlaceDetailResponse response = placeService.getPlaceDetailWithDate(placeId, targetDate); return ResponseEntity.ok(ApiResponse.success(response)); } } From 3bf712f73ed34c2d268bf7fca682cf0dc7644e99 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:31:28 +0900 Subject: [PATCH 35/42] =?UTF-8?q?[feat]=20#32=20-=20WebConfig=EC=97=90=20P?= =?UTF-8?q?ATCH=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kernellabs/kernellabs/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java index e800d5f..e602647 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java +++ b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java @@ -10,7 +10,7 @@ public class WebConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:5173", "https://kernel-labs-fee.vercel.app/", "https://uscode.porogramr.site/") - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") .allowedHeaders("*") .allowCredentials(true); } From 9dff47c8b7977f496f4b6400fb1d7094e6abcbd4 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:31:45 +0900 Subject: [PATCH 36/42] =?UTF-8?q?[fix]=20#32=20-=20response=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(status=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/dto/response/TimeSlotResponse.java | 3 ++- .../presentation/dto/response/enums/SlotStatus.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java index 764ac2f..c59c6f5 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java @@ -1,5 +1,6 @@ package com.kernellabs.kernellabs.presentation.dto.response; +import com.kernellabs.kernellabs.presentation.dto.response.enums.SlotStatus; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,6 +9,6 @@ public class TimeSlotResponse { private String time; - private boolean isAvailable; + private SlotStatus status; } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java new file mode 100644 index 0000000..6ed4116 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java @@ -0,0 +1,7 @@ +package com.kernellabs.kernellabs.presentation.dto.response.enums; + +public enum SlotStatus { + AVAILABLE, // μ˜ˆμ•½ κ°€λŠ₯ (아무도 μ˜ˆμ•½ μ•ˆ 함) + UNAVAILABLE, // μ˜ˆμ•½ λΆˆκ°€ (λ‹€λ₯Έ μ‚¬λžŒμ΄ μ˜ˆμ•½ν•¨) + MY_RESERVATION // λ‚˜μ˜ μ˜ˆμ•½ (선택/ν•΄μ œ κ°€λŠ₯) +} From b8ae8d1197a307477b121b27692249d341019af0 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:32:30 +0900 Subject: [PATCH 37/42] =?UTF-8?q?[fix]=20#32=20-=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=8C=80=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(=EC=98=88=EC=95=BD=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=8B=9C=20=EC=A0=91=EA=B7=BC=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/PlaceService.java | 72 +++++++++++++------ .../repository/ReservationRepository.java | 3 + .../controller/PlaceController.java | 5 +- 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java index d75ce64..d8161a6 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java @@ -9,12 +9,15 @@ import com.kernellabs.kernellabs.presentation.dto.response.PlaceDetailResponse; import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; import com.kernellabs.kernellabs.presentation.dto.response.TimeSlotResponse; +import com.kernellabs.kernellabs.presentation.dto.response.enums.SlotStatus; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +36,7 @@ public List getAllPlace() { .collect(Collectors.toList()); } - public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date) { + public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date, Long editingReservationId) { // 1. μž₯μ†Œ 정보 쑰회 Place place = placeRepository.findById(placeId) .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); @@ -42,39 +45,68 @@ public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date) LocalTime openTime = place.getOpenTime(); LocalTime closeTime = place.getCloseTime(); - // 3. ν•΄λ‹Ή λ‚ μ§œμ— 이미 μ˜ˆμ•½λœ μ‹œκ°„ λͺ©λ‘ 쑰회 - List reservations = reservationRepository.findByPlaceIdAndReservationDate(placeId, date); - Set reservedSlots = getReservedSlots(reservations); + // 3. ν•΄λ‹Ή λ‚ μ§œμ˜ 'λͺ¨λ“ ' μ˜ˆμ•½μ„ 일단 λ‹€ κ°€μ Έμ˜¨λ‹€. + List allReservationsOnDate = reservationRepository.findByPlaceIdAndReservationDate(placeId, date); - // 4. 전체 μ‹œκ°„ 슬둯 생성 및 μ˜ˆμ•½ κ°€λŠ₯ μ—¬λΆ€ νŒλ‹¨ - List timeSlots = generateTimeSlots(openTime, closeTime, reservedSlots); + // 4. 'λ‚˜μ˜ μ˜ˆμ•½' μ‹œκ°„κ³Ό 'λ‹€λ₯Έ μ‚¬λžŒ μ˜ˆμ•½' μ‹œκ°„μ„ λΆ„λ¦¬ν•˜μ—¬ Set으둜 λ§Œλ“ λ‹€. + Set myReservedSlots = getSlotsForSpecificReservation(allReservationsOnDate, editingReservationId); + Set othersReservedSlots = getSlotsForOtherReservations(allReservationsOnDate, editingReservationId); - // 5. μ΅œμ’… 응닡 DTO 생성 및 λ°˜ν™˜ + // 5. 3κ°€μ§€ μƒνƒœλ₯Ό ν¬ν•¨ν•œ 전체 μ‹œκ°„ 슬둯 리슀트λ₯Ό μƒμ„±ν•œλ‹€. + List timeSlots = generateTimeSlotsWithStatus(openTime, closeTime, myReservedSlots, othersReservedSlots); + + // 6. μ΅œμ’… 응닡 DTOλ₯Ό λ§Œλ“€μ–΄ λ°˜ν™˜ν•œλ‹€. return PlaceDetailResponse.of(place, timeSlots); } - private List generateTimeSlots(LocalTime openTime, LocalTime closeTime, Set reservedSlots) { + private List generateTimeSlotsWithStatus(LocalTime openTime, LocalTime closeTime, Set mySlots, Set otherSlots) { List slots = new ArrayList<>(); LocalTime currentTime = openTime; while (!currentTime.isAfter(closeTime.minusHours(1))) { - boolean isAvailable = !reservedSlots.contains(currentTime); - slots.add(new TimeSlotResponse(currentTime.toString(), isAvailable)); + SlotStatus status; + if (mySlots.contains(currentTime)) { + status = SlotStatus.MY_RESERVATION; + } else if (otherSlots.contains(currentTime)) { + status = SlotStatus.UNAVAILABLE; + } else { + status = SlotStatus.AVAILABLE; + } + slots.add(new TimeSlotResponse(currentTime.toString(), status)); currentTime = currentTime.plusHours(1); } return slots; } - private Set getReservedSlots(List reservations) { + private Set getSlotsForSpecificReservation(List reservations, Long reservationId) { + if (reservationId == null) { + return Collections.emptySet(); + } return reservations.stream() - .flatMap(reservation -> { - List slots = new ArrayList<>(); - LocalTime current = reservation.getStartTime(); - while (current.isBefore(reservation.getEndTime())) { - slots.add(current); - current = current.plusHours(1); - } - return slots.stream(); - }) + .filter(r -> r.getId().equals(reservationId)) + .flatMap(this::expandReservationToSlots) .collect(Collectors.toSet()); } + private Set getSlotsForOtherReservations(List reservations, Long reservationId) { + // 'λ‚˜μ˜ μ˜ˆμ•½ ID'κ°€ μ—†λŠ” 경우(μ‹ κ·œ μ˜ˆμ•½ λͺ¨λ“œ)μ—λŠ” λͺ¨λ“  μ˜ˆμ•½μ΄ 'λ‹€λ₯Έ μ‚¬λžŒ μ˜ˆμ•½'이 λœλ‹€. + if (reservationId == null) { + return reservations.stream() + .flatMap(this::expandReservationToSlots) + .collect(Collectors.toSet()); + } + // 'λ‚˜μ˜ μ˜ˆμ•½ ID'κ°€ μžˆλŠ” 경우(μˆ˜μ • λͺ¨λ“œ)μ—λŠ” ν•΄λ‹Ή μ˜ˆμ•½μ„ μ œμ™Έν•œλ‹€. + return reservations.stream() + .filter(r -> !r.getId().equals(reservationId)) + .flatMap(this::expandReservationToSlots) + .collect(Collectors.toSet()); + } + + private Stream expandReservationToSlots(Reservation reservation) { + List slots = new ArrayList<>(); + LocalTime current = reservation.getStartTime(); + while (current.isBefore(reservation.getEndTime())) { + slots.add(current); + current = current.plusHours(1); + } + return slots.stream(); + } } diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java index a1fba65..f74284d 100644 --- a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java @@ -29,4 +29,7 @@ boolean existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAft ); List findByPlaceIdAndReservationDate(Long placeId, LocalDate date); + + // νŠΉμ • μž₯μ†Œμ™€ λ‚ μ§œμ˜ λͺ¨λ“  μ˜ˆμ•½ 쑰회 (νŠΉμ • μ˜ˆμ•½μ„ μ œμ™Έ) + List findByPlaceIdAndReservationDateAndIdNot(Long placeId, LocalDate date, Long reservationId); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java index b83475b..49ddc7d 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java @@ -31,11 +31,12 @@ public ResponseEntity getAllPlaces() { @GetMapping("/{placeId}") public ResponseEntity> getPlace(@PathVariable Long placeId, - @RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate date) { + @RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate date, + @RequestParam(required = false) Long editingReservationId) { // λ‚ μ§œ νŒŒλΌλ―Έν„°κ°€ μ—†μœΌλ©΄ 였늘 λ‚ μ§œ κΈ°λ³Έκ°’ LocalDate targetDate = (date == null) ? LocalDate.now() : date; - PlaceDetailResponse response = placeService.getPlaceDetailWithDate(placeId, targetDate); + PlaceDetailResponse response = placeService.getPlaceDetailWithDate(placeId, targetDate, editingReservationId); return ResponseEntity.ok(ApiResponse.success(response)); } } From 28e67e41142b903c38fe6909f9ff501dd7663c49 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:37:59 +0900 Subject: [PATCH 38/42] =?UTF-8?q?[fix]=20#32=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReservationService.java | 15 ++++++++++++--- .../controller/ReservationController.java | 8 +++++--- .../dto/request/ReservationVerityRequest.java | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java index d4fad37..ab0728a 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -10,6 +10,7 @@ import com.kernellabs.kernellabs.presentation.dto.request.ReservationDeleteRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationVerityRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -22,6 +23,7 @@ @Service @RequiredArgsConstructor public class ReservationService { + private final ReservationRepository reservationRepository; private final PlaceRepository placeRepository; private final ReservationValidator reservationValidator; @@ -40,12 +42,19 @@ public ReservationResponse createReservation(ReservationRequest request) { request.getReservationDate(), request.getTimeSlots()); // 4. μ˜ˆμ•½ μ €μž₯ 및 응닡 λ°˜ν™˜ - reservationRepository.save(reservation); - return ReservationResponse.from(reservation); + reservationRepository.save(reservation); + return ReservationResponse.from(reservation); } - public ReservationResponse getReservation(Long reservationId) { + @Transactional + public ReservationResponse getReservation(Long reservationId, ReservationVerityRequest request) { + // 1. ID둜 μ˜ˆμ•½ μ°ΎκΈ° Reservation reservation = findReservationById(reservationId); + + // 2. λΉ„λ°€λ²ˆν˜Έμ™€ request password 비ꡐ + validatePassword(request.getPassword(), reservation.getPassword()); + + // 3.DTO λ³€ν™˜ return ReservationResponse.from(reservation); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java index 51445d0..41e5740 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -5,6 +5,7 @@ import com.kernellabs.kernellabs.presentation.dto.request.ReservationDeleteRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationVerityRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -31,9 +32,10 @@ public ResponseEntity> createReservation(@Reque return ResponseEntity.ok(ApiResponse.success(response)); } - @GetMapping("/{reservationId}") - public ResponseEntity> getReservation(@PathVariable Long reservationId) { - ReservationResponse response = reservationService.getReservation(reservationId); + @PostMapping("/{reservationId}") + public ResponseEntity> getReservation(@PathVariable Long reservationId, + @Valid @RequestBody ReservationVerityRequest request) { + ReservationResponse response = reservationService.getReservation(reservationId, request); return ResponseEntity.ok(ApiResponse.success(response)); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java new file mode 100644 index 0000000..122ea2f --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java @@ -0,0 +1,16 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationVerityRequest { + + @NotBlank(message = "λΉ„λ°€λ²ˆν˜ΈλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.") + @Pattern(regexp = "\\d{4}", message = "λΉ„λ°€λ²ˆν˜ΈλŠ” 4자리 μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.") + private String password; + +} From 3c128c4f9c4ea9d03bc07530b12c39c627c265bb Mon Sep 17 00:00:00 2001 From: HoyiTT Date: Sat, 28 Jun 2025 14:48:59 +0900 Subject: [PATCH 39/42] =?UTF-8?q?[feat]=20#29=20=EC=A0=9C=EB=AF=B8?= =?UTF-8?q?=EB=82=98=EC=9D=B4=20=EA=B7=B8=EB=9D=BC=EC=9A=B4=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../kernellabs/application/GeminiService.java | 46 +++++++++++++++++++ .../controller/GeminiController.java | 37 +++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/GeminiService.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java diff --git a/build.gradle b/build.gradle index 6bef8bf..2977bf9 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,13 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'com.google.genai:google-genai:1.6.0' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' runtimeOnly 'com.mysql:mysql-connector-j' + } tasks.named('test') { diff --git a/src/main/java/com/kernellabs/kernellabs/application/GeminiService.java b/src/main/java/com/kernellabs/kernellabs/application/GeminiService.java new file mode 100644 index 0000000..e10234e --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/GeminiService.java @@ -0,0 +1,46 @@ +package com.kernellabs.kernellabs.application; + +import autovalue.shaded.com.google.common.collect.ImmutableList; +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GoogleSearch; +import com.google.genai.types.Tool; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class GeminiService { + + private final Client client; + private final Tool googleSearchTool; + private final String modelName; + + public GeminiService( + @Value("${gemini.api.key}") String apiKey, + @Value("${gemini.model:gemini-2.5-flash}") String modelName + ) { + this.client = Client.builder() + .apiKey(apiKey) + .build(); + + this.googleSearchTool = Tool.builder() + .googleSearch(GoogleSearch.builder().build()) + .build(); + + this.modelName = modelName; + } + + public String generateAnswer(String prompt) { + GenerateContentConfig config = GenerateContentConfig.builder() + .tools(ImmutableList.of(googleSearchTool)) + .build(); + + // ← μ—¬κΈ°λ₯Ό client.models()κ°€ μ•„λ‹ˆλΌ client.models 둜 μ ‘κ·Ό + GenerateContentResponse res = client.models + .generateContent(modelName, prompt, config); + + return res.text(); + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java new file mode 100644 index 0000000..1ac63e8 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java @@ -0,0 +1,37 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import com.kernellabs.kernellabs.application.GeminiService; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/genie") +@AllArgsConstructor +public class GeminiController { + private final GeminiService geminiService; + + @PostMapping("/chat") + public ResponseEntity chat(@Valid @RequestBody ChatRequest req) { + String answer = geminiService.generateAnswer(req.getPrompt()); + return ResponseEntity.ok(new ChatResponse(answer)); + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ChatRequest { + private String prompt; + } + + @Data @NoArgsConstructor @AllArgsConstructor + public static class ChatResponse { + private String answer; + } +} From 721e22298143391cc37d9b11c97046f3290d3177 Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Sat, 28 Jun 2025 15:21:52 +0900 Subject: [PATCH 40/42] =?UTF-8?q?[feat]=20#29=20redis=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../kernellabs/global/config/RedisConfig.java | 41 +++++++++++++++++++ .../kernellabs/global/util/RedisUtil.java | 35 ++++++++++++++++ src/main/resources/application.yml | 6 +++ 4 files changed, 83 insertions(+) create mode 100644 src/main/java/com/kernellabs/kernellabs/global/config/RedisConfig.java create mode 100644 src/main/java/com/kernellabs/kernellabs/global/util/RedisUtil.java diff --git a/build.gradle b/build.gradle index 2977bf9..a706060 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'com.google.genai:google-genai:1.6.0' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/RedisConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/RedisConfig.java new file mode 100644 index 0000000..9c9680a --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/global/config/RedisConfig.java @@ -0,0 +1,41 @@ +package com.kernellabs.kernellabs.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Value("${spring.data.redis.password}") + private String password; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port); + redisStandaloneConfiguration.setHostName(host); + redisStandaloneConfiguration.setPort(port); + redisStandaloneConfiguration.setPassword(password); + return new LettuceConnectionFactory(redisStandaloneConfiguration); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); + return template; + } +} \ No newline at end of file diff --git a/src/main/java/com/kernellabs/kernellabs/global/util/RedisUtil.java b/src/main/java/com/kernellabs/kernellabs/global/util/RedisUtil.java new file mode 100644 index 0000000..6a84fcd --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/global/util/RedisUtil.java @@ -0,0 +1,35 @@ +package com.kernellabs.kernellabs.global.util; + +import java.time.Duration; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class RedisUtil { + + private final StringRedisTemplate redisTemplate; + + public String getData(String key) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + return valueOperations.get(key); + } + + public boolean existData(String key) { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } + + public void setDataExpire(String key, String value, long duration) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + Duration expireDuration = Duration.ofSeconds(duration); + valueOperations.set(key, value, expireDuration); + } + + public void deleteData(String key) { + redisTemplate.delete(key); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8765a96..417b50e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,6 +15,12 @@ spring: properties: hibernate: format_sql: true + + data: + redis: + host: ${REDIS_HOST} + password: ${REDIS_PASSWORD} + port: 6379 logging: level: root: INFO # 전체 둜그 μ΅œμ†Œ INFO 이상 기둝 :contentReference[oaicite:0]{index=0} From d856e8c3c71242ccb543d012619fdd0d3f60eb5f Mon Sep 17 00:00:00 2001 From: sohyeonjung Date: Sat, 28 Jun 2025 15:26:06 +0900 Subject: [PATCH 41/42] =?UTF-8?q?[feat]=20#29=20gemini=20search=20policy?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/PolicyService.java | 41 +++++++++++++++++++ .../external/GeminiSearchClient.java} | 6 +-- .../controller/GeminiController.java | 6 +-- .../controller/PolicyController.java | 27 ++++++++++++ .../dto/response/PolicyResponse.java | 10 +++++ 5 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/application/PolicyService.java rename src/main/java/com/kernellabs/kernellabs/{application/GeminiService.java => infrastructure/external/GeminiSearchClient.java} (91%) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/controller/PolicyController.java create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PolicyResponse.java diff --git a/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java b/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java new file mode 100644 index 0000000..55af016 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java @@ -0,0 +1,41 @@ +package com.kernellabs.kernellabs.application; + +import org.springframework.stereotype.Service; + +import com.kernellabs.kernellabs.global.util.RedisUtil; +import com.kernellabs.kernellabs.infrastructure.external.GeminiApiClient; +import com.kernellabs.kernellabs.infrastructure.external.GeminiSearchClient; +import com.kernellabs.kernellabs.presentation.dto.response.PolicyResponse; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PolicyService { + + private final String POLICY_REDIS_KEY = "policy"; + private final RedisUtil redisUtil; + private final GeminiSearchClient geminiSearchClient; + private final String prompt = """ + μ˜μ„±κ΅°μœΌλ‘œ μ΄μ£Όν•˜λ €λŠ” μ‚¬λžŒλ“€μ„ μœ„ν•œ μ΅œμ‹  ν˜œνƒ 정보λ₯Ό μ•Œλ €μ€˜. + μ£Όκ±°, ꡐ윑, κ·€λ†κ·€μ΄Œ, 볡지, μ°½μ—…, 일자리 μ§€μ›κΈˆ, μ •μ°©κΈˆ λ“± λͺ¨λ“  μ’…λ₯˜μ˜ 이주 및 μ •μ°© ν˜œνƒμ„ ν¬ν•¨ν•΄μ€˜. + 각 ν˜œνƒλ³„λ‘œ 지원 쑰건, μ‹ μ²­ 방법, λ‹΄λ‹Ή λΆ€μ„œ λ˜λŠ” κ΄€λ ¨ μ›Ήμ‚¬μ΄νŠΈ 링크 같은 상세 정보도 μ•Œλ €μ€˜. + 정보λ₯Ό λŒ€μ£Όμ œμ™€ μ†Œμ£Όμ œλ‘œ λ‚˜λˆ μ„œ μ •λ¦¬ν•΄μ€˜. + 각 μ†Œμ£Όμ œ μ•„λž˜μ— μžμ„Έν•œ μ„€λͺ…을 μΆ”κ°€ν•˜κ³ , κ΄€λ ¨ 링크가 μžˆλ‹€λ©΄ URL μ£Όμ†Œλ₯Ό λͺ…ν™•νžˆ ν¬ν•¨ν•΄μ€˜. + 각 ν•­λͺ©μ€ 쀄 λ°”κΏˆ(\n)을 μ‚¬μš©ν•΄μ„œ κ΅¬λΆ„ν•΄μ€˜. + λ‹€λ₯Έ μ§€μ—­ μ •λ³΄λ‚˜ 일반적인 λ‚΄μš©μ€ μ œμ™Έν•˜κ³ , 였직 μ˜μ„±κ΅° κ΄€λ ¨ ν˜œνƒλ§Œ λ‹€λ€„μ€˜. + """; + + public PolicyResponse getCurrentPolicy() { + String result = ""; + if(redisUtil.getData(POLICY_REDIS_KEY).isEmpty()){ + result = geminiSearchClient.generateAnswer(prompt); + redisUtil.setDataExpire(POLICY_REDIS_KEY, result, 1000); + } + else{ + result = redisUtil.getData(POLICY_REDIS_KEY); + } + return PolicyResponse.builder().policy(result).build(); + + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/application/GeminiService.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiSearchClient.java similarity index 91% rename from src/main/java/com/kernellabs/kernellabs/application/GeminiService.java rename to src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiSearchClient.java index e10234e..b53be79 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/GeminiService.java +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/external/GeminiSearchClient.java @@ -1,4 +1,4 @@ -package com.kernellabs.kernellabs.application; +package com.kernellabs.kernellabs.infrastructure.external; import autovalue.shaded.com.google.common.collect.ImmutableList; import com.google.genai.Client; @@ -10,13 +10,13 @@ import org.springframework.stereotype.Service; @Service -public class GeminiService { +public class GeminiSearchClient { private final Client client; private final Tool googleSearchTool; private final String modelName; - public GeminiService( + public GeminiSearchClient( @Value("${gemini.api.key}") String apiKey, @Value("${gemini.model:gemini-2.5-flash}") String modelName ) { diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java index 1ac63e8..67575ae 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/GeminiController.java @@ -1,6 +1,6 @@ package com.kernellabs.kernellabs.presentation.controller; -import com.kernellabs.kernellabs.application.GeminiService; +import com.kernellabs.kernellabs.infrastructure.external.GeminiSearchClient; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import lombok.Data; @@ -15,11 +15,11 @@ @RequestMapping("/api/genie") @AllArgsConstructor public class GeminiController { - private final GeminiService geminiService; + private final GeminiSearchClient geminiSearchClient; @PostMapping("/chat") public ResponseEntity chat(@Valid @RequestBody ChatRequest req) { - String answer = geminiService.generateAnswer(req.getPrompt()); + String answer = geminiSearchClient.generateAnswer(req.getPrompt()); return ResponseEntity.ok(new ChatResponse(answer)); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PolicyController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PolicyController.java new file mode 100644 index 0000000..55505d3 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PolicyController.java @@ -0,0 +1,27 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.kernellabs.kernellabs.application.PolicyService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +import com.kernellabs.kernellabs.presentation.dto.response.PolicyResponse; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/policies") +@RequiredArgsConstructor +public class PolicyController { + private final PolicyService policyService; + + @GetMapping("") + public ResponseEntity> getCurrentPolicy() { + PolicyResponse policyResponse = policyService.getCurrentPolicy(); + return ResponseEntity.ok(ApiResponse.success(policyResponse)); + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PolicyResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PolicyResponse.java new file mode 100644 index 0000000..197ad56 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/PolicyResponse.java @@ -0,0 +1,10 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PolicyResponse { + private String policy; +} From f569f33fcd9ed59d5b8b18f038707075341b3f61 Mon Sep 17 00:00:00 2001 From: HoyiTT Date: Sat, 28 Jun 2025 15:37:51 +0900 Subject: [PATCH 42/42] =?UTF-8?q?[feat]#29=20=EC=8B=A4=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=EB=A0=88=EB=94=94=EC=8A=A4=20=EC=BA=90?= =?UTF-8?q?=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/kernellabs/application/PolicyService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java b/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java index 55af016..2498692 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/PolicyService.java @@ -28,12 +28,12 @@ public class PolicyService { public PolicyResponse getCurrentPolicy() { String result = ""; - if(redisUtil.getData(POLICY_REDIS_KEY).isEmpty()){ - result = geminiSearchClient.generateAnswer(prompt); - redisUtil.setDataExpire(POLICY_REDIS_KEY, result, 1000); + if(redisUtil.existData(POLICY_REDIS_KEY)){ + result = redisUtil.getData(POLICY_REDIS_KEY); } else{ - result = redisUtil.getData(POLICY_REDIS_KEY); + result = geminiSearchClient.generateAnswer(prompt); + redisUtil.setDataExpire(POLICY_REDIS_KEY, result, 10800); } return PolicyResponse.builder().policy(result).build();