From 526598a24f96b0be3edf7aee2605beb6fa73770f Mon Sep 17 00:00:00 2001 From: pricelees Date: Wed, 30 Jul 2025 15:45:00 +0900 Subject: [PATCH 01/25] =?UTF-8?q?fix:=20frontend=EC=97=90=EC=84=9C?= =?UTF-8?q?=EC=9D=98=20=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Layout.tsx | 2 +- frontend/src/components/Navbar.tsx | 5 ++--- frontend/src/context/AuthContext.tsx | 6 +++--- frontend/src/pages/admin/AdminLayout.tsx | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 9c736580..e4440f76 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { type ReactNode } from 'react'; import Navbar from './Navbar'; interface LayoutProps { diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 83c75a2c..6d6a324d 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,12 +1,11 @@ -import { checkLogin } from '@_api/auth/authAPI'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from 'src/context/AuthContext'; const Navbar: React.FC = () => { const { loggedIn, userName, logout } = useAuth(); const navigate = useNavigate(); - + const handleLogout = async (e: React.MouseEvent) => { e.preventDefault(); try { diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index b4e83a61..c367b18f 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -1,13 +1,13 @@ -import React, { createContext, useState, useEffect, ReactNode, useContext } from 'react'; import { checkLogin as apiCheckLogin, login as apiLogin, logout as apiLogout } from '@_api/auth/authAPI'; -import type { LoginRequest, LoginCheckResponse } from '@_api/auth/authTypes'; +import type { LoginRequest, LoginResponse } from '@_api/auth/authTypes'; +import React, { createContext, useContext, useEffect, useState, type ReactNode } from 'react'; interface AuthContextType { loggedIn: boolean; userName: string | null; role: 'ADMIN' | 'MEMBER' | null; loading: boolean; // Add loading state to type - login: (data: LoginRequest) => Promise; + login: (data: LoginRequest) => Promise; logout: () => Promise; checkLogin: () => Promise; } diff --git a/frontend/src/pages/admin/AdminLayout.tsx b/frontend/src/pages/admin/AdminLayout.tsx index 26987dbe..47034a06 100644 --- a/frontend/src/pages/admin/AdminLayout.tsx +++ b/frontend/src/pages/admin/AdminLayout.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { type ReactNode } from 'react'; import AdminNavbar from './AdminNavbar'; interface AdminLayoutProps { -- 2.47.2 From 0c83798b3f21de164551c3a43c522681de05e1ad Mon Sep 17 00:00:00 2001 From: pricelees Date: Wed, 30 Jul 2025 15:45:35 +0900 Subject: [PATCH 02/25] =?UTF-8?q?feat:=20frontend=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?Dockerfile=20=EB=B0=8F=20nginx=20=EC=84=A4=EC=A0=95=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 --- frontend/.dockerignore | 6 ++++++ frontend/Dockerfile | 18 ++++++++++++++++++ frontend/nginx.conf | 15 +++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..3dd9fe5b --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,6 @@ +node_modules +.git +.DS_Store +npm-debug.log +dist +build \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..cfc4d6d9 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,18 @@ +# Stage 1: Build the React app +FROM node:24 AS builder +WORKDIR /app +COPY package.json ./ +COPY package-lock.json ./ + +RUN npm install --frozen-lockfile + +COPY . . + +RUN npm run build + +# Stage 2: Serve with Nginx +FROM nginx:latest +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 00000000..f304b884 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,15 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} -- 2.47.2 From d96c890dc0e0349f3cb6cc27df27ec796327fae4 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 31 Jul 2025 16:49:02 +0900 Subject: [PATCH 03/25] =?UTF-8?q?feat:=20=EB=B0=B0=ED=8F=AC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=EC=9D=98=20Slow=20Query=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=A4=EC=A0=95=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 --- .../MDCAwareSlowQueryListenerWithoutParams.kt | 29 ++++++++++++ .../common/log/ProxyDataSourceConfig.kt | 46 +++++++++++++++++++ .../log/RoomescapeLogMaskingConverter.kt | 2 +- src/main/resources/application-local.yaml | 3 +- src/main/resources/logback-local.xml | 2 +- 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt create mode 100644 src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt diff --git a/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt b/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt new file mode 100644 index 00000000..a1084f1e --- /dev/null +++ b/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt @@ -0,0 +1,29 @@ +package roomescape.common.log + +import net.ttddyy.dsproxy.ExecutionInfo +import net.ttddyy.dsproxy.QueryInfo +import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel +import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener + +class MDCAwareSlowQueryListenerWithoutParams( + private val thresholdMs: Long +) : SLF4JQueryLoggingListener() { + + init { + this.logLevel = SLF4JLogLevel.WARN + } + + override fun afterQuery( + execInfo: ExecutionInfo, + queryInfoList: List + ) { + if (execInfo.elapsedTime >= thresholdMs) { + super.afterQuery(execInfo, queryInfoList) + } + } + + override fun writeLog(message: String) { + val modified = message.replace(Regex("""(,?\s*)Params:\[.*?]"""), "") + super.writeLog(modified) + } +} diff --git a/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt b/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt new file mode 100644 index 00000000..4038b3fd --- /dev/null +++ b/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt @@ -0,0 +1,46 @@ +package roomescape.common.log + +import com.zaxxer.hikari.HikariDataSource +import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.jdbc.DataSourceBuilder +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary +import org.springframework.context.annotation.Profile +import javax.sql.DataSource + +@Configuration +@Profile("deploy") +@EnableConfigurationProperties(SlowQueryProperties::class) +class ProxyDataSourceConfig { + + @Bean + @Primary + fun dataSource( + @Qualifier("actualDataSource") actualDataSource: DataSource, + properties: SlowQueryProperties + ): DataSource = ProxyDataSourceBuilder.create(actualDataSource) + .name(properties.loggerName) + .listener( + MDCAwareSlowQueryListenerWithoutParams( + properties.thresholdMs + ) + ) + .buildProxy() + + @Bean + @ConfigurationProperties(prefix = "spring.datasource.hikari") + fun actualDataSource(): DataSource = DataSourceBuilder.create() + .type(HikariDataSource::class.java) + .build() +} + +@Profile("deploy") +@ConfigurationProperties(prefix = "slow-query") +data class SlowQueryProperties( + val loggerName: String, + val thresholdMs: Long, +) diff --git a/src/main/kotlin/roomescape/common/log/RoomescapeLogMaskingConverter.kt b/src/main/kotlin/roomescape/common/log/RoomescapeLogMaskingConverter.kt index 3fb696b1..2c94c436 100644 --- a/src/main/kotlin/roomescape/common/log/RoomescapeLogMaskingConverter.kt +++ b/src/main/kotlin/roomescape/common/log/RoomescapeLogMaskingConverter.kt @@ -36,7 +36,7 @@ class RoomescapeLogMaskingConverter : MessageConverter() { private fun maskedPlainMessage(message: String): String { val keys: String = SENSITIVE_KEYS.joinToString("|") - val regex = Regex("(?i)($keys)(\\s*=\\s*)([^,\\s]+)") + val regex = Regex("(?i)($keys)(\\s*=\\s*)([^(,|\"|?)\\s]+)") return regex.replace(message) { matchResult -> val key = matchResult.groupValues[1] diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 9a8b7f2d..52d1151c 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,6 +1,5 @@ spring: jpa: - show-sql: false properties: hibernate: format_sql: true @@ -35,7 +34,7 @@ jdbc: query: enable-logging: true log-level: DEBUG - logger-name: query-logger + logger-name: all-query-logger multiline: true includes: connection,query,keys,fetch diff --git a/src/main/resources/logback-local.xml b/src/main/resources/logback-local.xml index fb0a2202..f94aa623 100644 --- a/src/main/resources/logback-local.xml +++ b/src/main/resources/logback-local.xml @@ -20,7 +20,7 @@ - + -- 2.47.2 From 6db8730764ab270f7ffc0c70accbeaabd36bf507 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 31 Jul 2025 16:52:51 +0900 Subject: [PATCH 04/25] =?UTF-8?q?refactor:=20ReservationEntity=EC=9D=98=20?= =?UTF-8?q?not=20null=20=EC=BB=AC=EB=9F=BC=20=EC=A7=80=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20reservationStatus=20->=20status=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/reservation/business/ReservationService.kt | 8 ++++---- .../infrastructure/persistence/ReservationEntity.kt | 6 ++++-- .../infrastructure/persistence/ReservationRepository.kt | 6 +++--- .../roomescape/reservation/web/ReservationResponse.kt | 2 +- .../persistence/ReservationRepositoryTest.kt | 2 +- .../reservation/web/ReservationControllerTest.kt | 6 +++--- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/roomescape/reservation/business/ReservationService.kt b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt index a5374f75..5f17450f 100644 --- a/src/main/kotlin/roomescape/reservation/business/ReservationService.kt +++ b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt @@ -79,7 +79,7 @@ class ReservationService( createEntity(timeId, themeId, date, memberId, ReservationStatus.CONFIRMED) return reservationRepository.save(reservation) - .also { log.info { "[ReservationService.createConfirmedReservation] 예약 추가 완료: reservationId=${it.id}, status=${it.reservationStatus}" } } + .also { log.info { "[ReservationService.createConfirmedReservation] 예약 추가 완료: reservationId=${it.id}, status=${it.status}" } } } fun createReservationByAdmin(request: AdminReservationCreateRequest): ReservationRetrieveResponse { @@ -192,7 +192,7 @@ class ReservationService( time = time, theme = theme, member = member, - reservationStatus = status + status = status ) } @@ -259,7 +259,7 @@ class ReservationService( if (!reservation.isWaiting()) { log.warn { "[ReservationService.deleteWaiting] 대기 취소 실패(대기 예약이 아님): reservationId=$reservationId" + - ", currentStatus=${reservation.reservationStatus} memberId=$memberId" + ", currentStatus=${reservation.status} memberId=$memberId" } throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED) } @@ -284,7 +284,7 @@ class ReservationService( if (!reservation.isWaiting()) { log.warn { "[ReservationService.rejectWaiting] 대기 예약 삭제 실패(이미 확정 상태): reservationId=$reservationId" + - ", status=${reservation.reservationStatus}" + ", status=${reservation.status}" } throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED) } diff --git a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt index 53939054..0ae9d2fa 100644 --- a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt @@ -14,6 +14,7 @@ class ReservationEntity( @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, + @Column(name = "date", nullable = false) var date: LocalDate, @ManyToOne(fetch = FetchType.LAZY) @@ -29,10 +30,11 @@ class ReservationEntity( var member: MemberEntity, @Enumerated(value = EnumType.STRING) - var reservationStatus: ReservationStatus + @Column(name = "status", nullable = false) + var status: ReservationStatus ) { @JsonIgnore - fun isWaiting(): Boolean = reservationStatus == ReservationStatus.WAITING + fun isWaiting(): Boolean = status == ReservationStatus.WAITING @JsonIgnore fun isReservedBy(memberId: Long): Boolean { diff --git a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt index c11891a7..e9f6c053 100644 --- a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt @@ -19,7 +19,7 @@ interface ReservationRepository @Query( """ UPDATE ReservationEntity r - SET r.reservationStatus = :status + SET r.status = :status WHERE r.id = :id """ ) @@ -39,7 +39,7 @@ interface ReservationRepository WHERE r.theme.id = r2.theme.id AND r.time.id = r2.time.id AND r.date = r2.date - AND r.reservationStatus != 'WAITING' + AND r.status != 'WAITING' ) ) """ @@ -53,7 +53,7 @@ interface ReservationRepository t.name, r.date, r.time.startAt, - r.reservationStatus, + r.status, (SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.time = r.time AND r2.id < r.id), p.paymentKey, p.totalAmount diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt b/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt index 5e4ea6c0..7b733dac 100644 --- a/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt +++ b/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt @@ -54,7 +54,7 @@ fun ReservationEntity.toRetrieveResponse(): ReservationRetrieveResponse = Reserv member = this.member.toRetrieveResponse(), time = this.time.toCreateResponse(), theme = this.theme.toResponse(), - status = this.reservationStatus + status = this.status ) data class ReservationRetrieveListResponse( diff --git a/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt index d1c095ab..055ed019 100644 --- a/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt +++ b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt @@ -106,7 +106,7 @@ class ReservationRepositoryTest( entityManager.clear() reservationRepository.findByIdOrNull(reservationId)?.also { updated -> - updated.reservationStatus shouldBe it + updated.status shouldBe it } } } diff --git a/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt index 054ffe3b..0ebaad3b 100644 --- a/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt +++ b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt @@ -331,7 +331,7 @@ class ReservationControllerTest( ReservationEntity::class.java, reservationId ) - reservation.reservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED + reservation.status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED entityManager.persist(reservation) entityManager.flush() entityManager.clear() @@ -443,7 +443,7 @@ class ReservationControllerTest( test("대기 중인 예약 목록을 조회한다.") { login(MemberFixture.create(role = Role.ADMIN)) val expected = reservations.values.flatten() - .count { it.reservationStatus == ReservationStatus.WAITING } + .count { it.status == ReservationStatus.WAITING } Given { port(port) @@ -601,7 +601,7 @@ class ReservationControllerTest( ReservationEntity::class.java, reservation.id )?.also { - it.reservationStatus shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED + it.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED } ?: throw AssertionError("Reservation not found") } } -- 2.47.2 From b986a2000472c08a5aada6811eace6b83366a828 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 13:52:26 +0900 Subject: [PATCH 05/25] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=8F=20=EB=A1=9C=EC=BB=AC=EC=9D=98=20application.yaml?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-local.yaml | 12 ++++++------ src/test/resources/application.yaml | 4 ---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 52d1151c..fb0a7f19 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -3,18 +3,18 @@ spring: properties: hibernate: format_sql: true + hibernate: ddl-auto: create-drop - defer-datasource-initialization: true - h2: console: enabled: true path: /h2-console datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:database - username: sa - password: + hikari: + jdbc-url: jdbc:h2:mem:database + driver-class-name: org.h2.Driver + username: sa + password: security: jwt: diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 98b14091..7e7ce703 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -5,10 +5,6 @@ spring: hibernate: format_sql: true ddl-auto: create-drop - defer-datasource-initialization: true - sql: - init: - data-locations: security: jwt: -- 2.47.2 From 9a38d8beb8f679ea4f7166f829727541098bb5cd Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 13:52:49 +0900 Subject: [PATCH 06/25] =?UTF-8?q?remove:=20=EC=B4=88=EA=B8=B0=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=82=BD=EC=9E=85=EC=9A=A9=20sql=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20->=20=EC=BD=94=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=82=BD=EC=9E=85=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/data.sql | 68 ------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 src/main/resources/data.sql diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index 8c38d67d..00000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -1,68 +0,0 @@ -insert into times(start_at) -values ('15:00'); -insert into times(start_at) -values ('16:00'); -insert into times(start_at) -values ('17:00'); -insert into times(start_at) -values ('18:00'); - -insert into themes(name, description, thumbnail) -values ('테스트1', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg'); -insert into themes(name, description, thumbnail) -values ('테스트2', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg'); -insert into themes(name, description, thumbnail) -values ('테스트3', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg'); -insert into themes(name, description, thumbnail) -values ('테스트4', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg'); - -insert into members(name, email, password, role) -values ('어드민', 'a@a.a', 'a', 'ADMIN'); -insert into members(name, email, password, role) -values ('1호', '1@1.1', '1', 'MEMBER'); -insert into members(name, email, password, role) -values ('2호', '2@2.2', '2', 'MEMBER'); -insert into members(name, email, password, role) -values ('3호', '3@3.3', '3', 'MEMBER'); -insert into members(name, email, password, role) -values ('4호', '4@4.4', '4', 'MEMBER'); - --- 예약: 결제 완료 -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (1, DATEADD('DAY', -1, CURRENT_DATE()) - 1, 1, 1, 'CONFIRMED'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (2, DATEADD('DAY', -2, CURRENT_DATE()) - 2, 3, 2, 'CONFIRMED'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (3, DATEADD('DAY', -3, CURRENT_DATE()), 2, 2, 'CONFIRMED'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (4, DATEADD('DAY', -4, CURRENT_DATE()), 1, 2, 'CONFIRMED'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (5, DATEADD('DAY', -5, CURRENT_DATE()), 1, 3, 'CONFIRMED'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (2, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'CONFIRMED'); - --- 예약: 결제 대기 -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (2, DATEADD('DAY', 8, CURRENT_DATE()), 2, 4, 'CONFIRMED_PAYMENT_REQUIRED'); - --- 예약 대기 -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (3, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (4, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING'); -insert into reservations(member_id, date, time_id, theme_id, reservation_status) -values (5, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING'); - --- 결제 정보 -insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at) -values ('orderId-1', 'paymentKey-1', 10000, 1, CURRENT_DATE); -insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at) -values ('orderId-2', 'paymentKey-2', 20000, 2, CURRENT_DATE); -insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at) -values ('orderId-3', 'paymentKey-3', 30000, 3, CURRENT_DATE); -insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at) -values ('orderId-4', 'paymentKey-4', 40000, 4, CURRENT_DATE); -insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at) -values ('orderId-5', 'paymentKey-5', 50000, 5, CURRENT_DATE); -insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at) -values ('orderId-6', 'paymentKey-6', 60000, 6, CURRENT_DATE); \ No newline at end of file -- 2.47.2 From e5002ae540ce25aa472b6952bb41fc7e5a5a7f43 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 13:53:12 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20mysql=20gradle=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index ae046cbe..10fcbf9d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { // DB runtimeOnly("com.h2database:h2") + runtimeOnly("com.mysql:mysql-connector-j") // Jwt implementation("io.jsonwebtoken:jjwt:0.12.6") -- 2.47.2 From 9e86222d5be14e43ff83ae3e7aa66c04349f23a2 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:49:01 +0900 Subject: [PATCH 08/25] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=A0=84=EC=9A=A9=20=EC=84=A4=EC=A0=95=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application-test.yaml | 5 +++++ src/test/resources/application.yaml | 25 ------------------------ 2 files changed, 5 insertions(+), 25 deletions(-) create mode 100644 src/test/resources/application-test.yaml delete mode 100644 src/test/resources/application.yaml diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml new file mode 100644 index 00000000..7a658a0e --- /dev/null +++ b/src/test/resources/application-test.yaml @@ -0,0 +1,5 @@ +logging: + level: + root: INFO + org.springframework.orm.jpa: INFO + org.springframework.transaction: DEBUG diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml deleted file mode 100644 index 7e7ce703..00000000 --- a/src/test/resources/application.yaml +++ /dev/null @@ -1,25 +0,0 @@ -spring: - jpa: - show-sql: false - properties: - hibernate: - format_sql: true - ddl-auto: create-drop - -security: - jwt: - token: - secret-key: daijawligagaf@LIJ$@U)9nagnalkkgalijaddljfi - ttl-seconds: 1800000 - -payment: - api-base-url: https://api.tosspayments.com - confirm-secret-key: test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6 - read-timeout: 3 - connect-timeout: 30 - -logging: - level: - root: INFO - org.springframework.orm.jpa: INFO - org.springframework.transaction: DEBUG -- 2.47.2 From d293b56b0fd08c21e5751f04bea53f45e0a3bba5 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:49:25 +0900 Subject: [PATCH 09/25] =?UTF-8?q?feat:=20=EC=8A=A4=ED=82=A4=EB=A7=88=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20ddl-auto?= =?UTF-8?q?=20validate=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-local.yaml | 5 +- src/main/resources/schema/schema-h2.sql | 63 ++++++++++++++++++++ src/main/resources/schema/schema-mysql.sql | 69 ++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/schema/schema-h2.sql create mode 100644 src/main/resources/schema/schema-mysql.sql diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index fb0a7f19..9d0c5b24 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -4,7 +4,7 @@ spring: hibernate: format_sql: true hibernate: - ddl-auto: create-drop + ddl-auto: validate h2: console: enabled: true @@ -15,6 +15,9 @@ spring: driver-class-name: org.h2.Driver username: sa password: + sql: + init: + schema-locations: classpath:schema/schema-h2.sql security: jwt: diff --git a/src/main/resources/schema/schema-h2.sql b/src/main/resources/schema/schema-h2.sql new file mode 100644 index 00000000..ce3b4fdd --- /dev/null +++ b/src/main/resources/schema/schema-h2.sql @@ -0,0 +1,63 @@ +create table if not exists members ( + member_id bigint primary key, + email varchar(255) not null, + name varchar(255) not null, + password varchar(255) not null, + role varchar(20) not null, + created_at timestamp null, + last_modified_at timestamp null +); + +create table if not exists themes ( + theme_id bigint primary key, + description varchar(255) not null, + name varchar(255) not null, + thumbnail varchar(255) not null, + created_at timestamp null, + last_modified_at timestamp null +); + +create table if not exists times ( + time_id bigint primary key, + start_at time not null, + created_at timestamp null, + last_modified_at timestamp null +); + +create table if not exists reservations ( + reservation_id bigint primary key, + date date not null, + member_id bigint not null, + theme_id bigint not null, + time_id bigint not null, + status varchar(30) not null, + created_at timestamp null, + last_modified_at timestamp null, + constraint fk_reservations__themeId foreign key (theme_id) references themes (theme_id), + constraint fk_reservations__memberId foreign key (member_id) references members (member_id), + constraint fk_reservations__timeId foreign key (time_id) references times (time_id) +); + +create table if not exists payments ( + payment_id bigint primary key, + approved_at timestamp not null, + reservation_id bigint not null, + total_amount bigint not null, + order_id varchar(255) not null, + payment_key varchar(255) not null, + created_at timestamp null, + last_modified_at timestamp null, + constraint uk_payments__reservationId unique (reservation_id), + constraint fk_payments__reservationId foreign key (reservation_id) references reservations (reservation_id) +); + +create table if not exists canceled_payments ( + canceled_payment_id bigint primary key, + payment_key varchar(255) not null, + cancel_reason varchar(255) not null, + cancel_amount bigint not null, + approved_at timestamp not null, + canceled_at timestamp not null, + created_at timestamp null, + last_modified_at timestamp null +); diff --git a/src/main/resources/schema/schema-mysql.sql b/src/main/resources/schema/schema-mysql.sql new file mode 100644 index 00000000..b13d5035 --- /dev/null +++ b/src/main/resources/schema/schema-mysql.sql @@ -0,0 +1,69 @@ +create table if not exists members +( + member_id bigint primary key, + email varchar(255) not null, + name varchar(255) not null, + password varchar(255) not null, + role varchar(20) not null, + created_at datetime null, + last_modified_at datetime null +); + +create table if not exists themes +( + theme_id bigint primary key, + description varchar(255) not null, + name varchar(255) not null, + thumbnail varchar(255) not null, + created_at datetime null, + last_modified_at datetime null +); + +create table if not exists times +( + time_id bigint primary key, + start_at time(6) not null, + created_at datetime null, + last_modified_at datetime null +); + +create table if not exists reservations +( + reservation_id bigint primary key, + date date not null, + member_id bigint not null, + theme_id bigint not null, + time_id bigint not null, + status varchar(30) not null, + created_at datetime null, + last_modified_at datetime null, + constraint fk_reservations__themeId foreign key (theme_id) references themes (theme_id), + constraint fk_reservations__memberId foreign key (member_id) references members (member_id), + constraint fk_reservations__timeId foreign key (time_id) references times (time_id) +); + +create table if not exists payments +( + payment_id bigint primary key, + approved_at datetime(6) not null, + reservation_id bigint not null, + total_amount bigint not null, + order_id varchar(255) not null, + payment_key varchar(255) not null, + created_at datetime null, + last_modified_at datetime null, + constraint uk_payments__reservationId unique (reservation_id), + constraint fk_payments__reservationId foreign key (reservation_id) references reservations (reservation_id) +); + +create table if not exists canceled_payments +( + canceled_payment_id bigint primary key, + payment_key varchar(255) not null, + cancel_reason varchar(255) not null, + cancel_amount bigint not null, + approved_at datetime(6) not null, + canceled_at datetime(6) not null, + created_at datetime null, + last_modified_at datetime null +); -- 2.47.2 From 2af0923189e5e736f7d0f8ce816070692b2cc233 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:49:52 +0900 Subject: [PATCH 10/25] =?UTF-8?q?refactor:=20MDCAwareSlowQueryListenerWith?= =?UTF-8?q?outParams=20=EC=97=90=EC=84=9C=20=EB=B3=80=ED=99=98,=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EB=A1=9C=EC=A7=81=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MDCAwareSlowQueryListenerWithoutParams.kt | 28 +++++++++++++------ ...AwareSlowQueryListenerWithoutParamsTest.kt | 26 +++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 src/test/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt diff --git a/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt b/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt index a1084f1e..bbc44024 100644 --- a/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt +++ b/src/main/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParams.kt @@ -4,26 +4,38 @@ import net.ttddyy.dsproxy.ExecutionInfo import net.ttddyy.dsproxy.QueryInfo import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener +import java.util.function.Predicate -class MDCAwareSlowQueryListenerWithoutParams( - private val thresholdMs: Long -) : SLF4JQueryLoggingListener() { +class MDCAwareSlowQueryListenerWithoutParams : SLF4JQueryLoggingListener { + private val slowQueryPredicate: SlowQueryPredicate + private val sqlLogFormatter: SqlLogFormatter - init { - this.logLevel = SLF4JLogLevel.WARN + constructor(logLevel: SLF4JLogLevel, thresholdMs: Long) { + this.logLevel = logLevel + this.slowQueryPredicate = SlowQueryPredicate(thresholdMs) + this.sqlLogFormatter = SqlLogFormatter() } override fun afterQuery( execInfo: ExecutionInfo, queryInfoList: List ) { - if (execInfo.elapsedTime >= thresholdMs) { + if (slowQueryPredicate.test(execInfo.elapsedTime)) { super.afterQuery(execInfo, queryInfoList) } } override fun writeLog(message: String) { - val modified = message.replace(Regex("""(,?\s*)Params:\[.*?]"""), "") - super.writeLog(modified) + super.writeLog(sqlLogFormatter.maskParams(message)) } } + +class SqlLogFormatter { + fun maskParams(message: String) = message.replace(Regex("""(,?\s*)Params:\[.*?]"""), "") +} + +class SlowQueryPredicate( + private val thresholdMs: Long +) : Predicate { + override fun test(t: Long): Boolean = (t >= thresholdMs) +} diff --git a/src/test/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt b/src/test/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt new file mode 100644 index 00000000..ac224f63 --- /dev/null +++ b/src/test/kotlin/roomescape/common/log/MDCAwareSlowQueryListenerWithoutParamsTest.kt @@ -0,0 +1,26 @@ +package roomescape.common.log + +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class MDCAwareSlowQueryListenerWithoutParamsTest : StringSpec({ + "SQL 메시지에서 Params 항목은 가린다." { + val message = """Query:["select * from members m where m.email=?"], Params:[(a@a.a)]""" + val expected = """Query:["select * from members m where m.email=?"]""" + val result = SqlLogFormatter().maskParams(message) + + result shouldBe expected + } + + "입력된 thresholdMs 보다 소요시간이 긴 쿼리를 기록한다." { + val slowQueryThreshold = 10L + val slowQueryPredicate = SlowQueryPredicate(thresholdMs = slowQueryThreshold) + + assertSoftly(slowQueryPredicate) { + it.test(slowQueryThreshold) shouldBe true + it.test(slowQueryThreshold + 1) shouldBe true + it.test(slowQueryThreshold - 1) shouldBe false + } + } +}) -- 2.47.2 From d2e2c9c888873f3b708e87fa35090b9e6a115410 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:51:11 +0900 Subject: [PATCH 11/25] =?UTF-8?q?refactor:=20PK=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95(Auto-Generated=20->=20T?= =?UTF-8?q?SID)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../roomescape/common/config/TsidConfig.kt | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/main/kotlin/roomescape/common/config/TsidConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index 10fcbf9d..bfec98c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9") // DB + implementation("com.github.f4b6a3:tsid-creator:5.2.6") runtimeOnly("com.h2database:h2") runtimeOnly("com.mysql:mysql-connector-j") diff --git a/src/main/kotlin/roomescape/common/config/TsidConfig.kt b/src/main/kotlin/roomescape/common/config/TsidConfig.kt new file mode 100644 index 00000000..ea84f02c --- /dev/null +++ b/src/main/kotlin/roomescape/common/config/TsidConfig.kt @@ -0,0 +1,17 @@ +package roomescape.common.config + +import com.github.f4b6a3.tsid.TsidFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class TsidConfig { + @Value("\${POD_NAME:app-0}") + private lateinit var podName: String + + @Bean + fun tsidFactory(): TsidFactory = TsidFactory(podName.substringAfterLast("-").toInt()) +} + +fun TsidFactory.next(): Long = this.create().toLong() \ No newline at end of file -- 2.47.2 From 769576a8d5821a4acad0e4c1897e4c7e2ed0e1cb Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:52:44 +0900 Subject: [PATCH 12/25] =?UTF-8?q?feat:=20Auditing=20=EB=B0=8F=20Persist=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9D=B4=20=EB=8B=B4=EA=B8=B4=20BaseEntity?= =?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 --- .../roomescape/RoomescapeApplication.kt | 2 ++ .../roomescape/common/entity/BaseEntity.kt | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/main/kotlin/roomescape/common/entity/BaseEntity.kt diff --git a/src/main/kotlin/roomescape/RoomescapeApplication.kt b/src/main/kotlin/roomescape/RoomescapeApplication.kt index aca20d20..be15c160 100644 --- a/src/main/kotlin/roomescape/RoomescapeApplication.kt +++ b/src/main/kotlin/roomescape/RoomescapeApplication.kt @@ -3,7 +3,9 @@ package roomescape import org.springframework.boot.Banner import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.data.jpa.repository.config.EnableJpaAuditing +@EnableJpaAuditing @SpringBootApplication class RoomescapeApplication diff --git a/src/main/kotlin/roomescape/common/entity/BaseEntity.kt b/src/main/kotlin/roomescape/common/entity/BaseEntity.kt new file mode 100644 index 00000000..439f62ef --- /dev/null +++ b/src/main/kotlin/roomescape/common/entity/BaseEntity.kt @@ -0,0 +1,35 @@ +package roomescape.common.entity + +import jakarta.persistence.* +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.domain.Persistable +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.LocalDateTime +import kotlin.jvm.Transient + +@MappedSuperclass +@EntityListeners(AuditingEntityListener::class) +abstract class BaseEntity( + @Column(updatable = false) + @CreatedDate + var createdAt: LocalDateTime? = null, + + @LastModifiedDate + var lastModifiedAt: LocalDateTime? = null, +) : Persistable { + + @Transient + private var isNewEntity: Boolean = true + + @PostLoad + @PostPersist + fun markNotNew() { + isNewEntity = false + } + + override fun isNew(): Boolean = isNewEntity + + abstract override fun getId(): Long? + +} -- 2.47.2 From 8a126344f0844e646f32f151c87819f317524433 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:53:25 +0900 Subject: [PATCH 13/25] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20BaseEntity=20=EB=B0=8F?= =?UTF-8?q?=20TSID=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/MemberEntity.kt | 16 ++++++++++--- .../persistence/CanceledPaymentEntity.kt | 23 +++++++++++++++---- .../persistence/PaymentEntity.kt | 17 ++++++++------ .../persistence/ReservationEntity.kt | 13 +++++++---- .../infrastructure/persistence/ThemeEntity.kt | 21 +++++++++++++---- .../infrastructure/persistence/TimeEntity.kt | 18 +++++++++++---- 6 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt index 75db9f8f..7ea5665d 100644 --- a/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt +++ b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt @@ -1,20 +1,30 @@ package roomescape.member.infrastructure.persistence import jakarta.persistence.* +import roomescape.common.entity.BaseEntity @Entity @Table(name = "members") class MemberEntity( @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, + @Column(name = "member_id") + private var _id: Long?, + + @Column(name = "name", nullable = false) var name: String, + + @Column(name = "email", nullable = false) var email: String, + + @Column(name = "password", nullable = false) var password: String, + @Column(name = "role", nullable = false, length = 20) @Enumerated(value = EnumType.STRING) var role: Role -) { +): BaseEntity() { + override fun getId(): Long? = _id + fun isAdmin(): Boolean = role == Role.ADMIN } diff --git a/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt b/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt index 6a581d55..133e3eb4 100644 --- a/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt @@ -1,18 +1,33 @@ package roomescape.payment.infrastructure.persistence -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import roomescape.common.entity.BaseEntity import java.time.OffsetDateTime @Entity @Table(name = "canceled_payments") class CanceledPaymentEntity( @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, + @Column(name = "canceled_payment_id") + private var _id: Long?, + @Column(name = "payment_key", nullable = false) var paymentKey: String, + + @Column(name = "cancel_reason", nullable = false) var cancelReason: String, + + @Column(name = "cancel_amount", nullable = false) var cancelAmount: Long, + + @Column(name = "approved_at", nullable = false) var approvedAt: OffsetDateTime, + + @Column(name = "canceled_at", nullable = false) var canceledAt: OffsetDateTime, -) +): BaseEntity() { + override fun getId(): Long? = _id +} diff --git a/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt b/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt index 59a89e8d..f3c80bd6 100644 --- a/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt @@ -1,6 +1,7 @@ package roomescape.payment.infrastructure.persistence import jakarta.persistence.* +import roomescape.common.entity.BaseEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity import java.time.OffsetDateTime @@ -8,22 +9,24 @@ import java.time.OffsetDateTime @Table(name = "payments") class PaymentEntity( @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, + @Column(name = "payment_id") + private var _id: Long?, - @Column(nullable = false) + @Column(name = "order_id", nullable = false) var orderId: String, - @Column(nullable = false) + @Column(name="payment_key", nullable = false) var paymentKey: String, - @Column(nullable = false) + @Column(name="total_amount", nullable = false) var totalAmount: Long, @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "reservation_id", nullable = false) var reservation: ReservationEntity, - @Column(nullable = false) + @Column(name="approved_at", nullable = false) var approvedAt: OffsetDateTime -) \ No newline at end of file +): BaseEntity() { + override fun getId(): Long? = _id +} diff --git a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt index 0ae9d2fa..e3c92f2f 100644 --- a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt @@ -2,6 +2,7 @@ package roomescape.reservation.infrastructure.persistence import com.fasterxml.jackson.annotation.JsonIgnore import jakarta.persistence.* +import roomescape.common.entity.BaseEntity import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.time.infrastructure.persistence.TimeEntity @@ -11,8 +12,8 @@ import java.time.LocalDate @Table(name = "reservations") class ReservationEntity( @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, + @Column(name = "reservation_id") + private var _id: Long?, @Column(name = "date", nullable = false) var date: LocalDate, @@ -30,9 +31,11 @@ class ReservationEntity( var member: MemberEntity, @Enumerated(value = EnumType.STRING) - @Column(name = "status", nullable = false) - var status: ReservationStatus -) { + @Column(name = "status", nullable = false, length = 30) + var status: ReservationStatus, +): BaseEntity() { + override fun getId(): Long? = _id + @JsonIgnore fun isWaiting(): Boolean = status == ReservationStatus.WAITING diff --git a/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt index acb6555b..4abd4383 100644 --- a/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt +++ b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt @@ -1,15 +1,26 @@ package roomescape.theme.infrastructure.persistence -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import roomescape.common.entity.BaseEntity @Entity @Table(name = "themes") class ThemeEntity( @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, + @Column(name = "theme_id") + private var _id: Long?, + @Column(name = "name", nullable = false) var name: String, + + @Column(name = "description", nullable = false) var description: String, - var thumbnail: String -) \ No newline at end of file + + @Column(name = "thumbnail", nullable = false) + var thumbnail: String, +): BaseEntity() { + override fun getId(): Long? = _id +} diff --git a/src/main/kotlin/roomescape/time/infrastructure/persistence/TimeEntity.kt b/src/main/kotlin/roomescape/time/infrastructure/persistence/TimeEntity.kt index f2f78706..3e31dd80 100644 --- a/src/main/kotlin/roomescape/time/infrastructure/persistence/TimeEntity.kt +++ b/src/main/kotlin/roomescape/time/infrastructure/persistence/TimeEntity.kt @@ -1,13 +1,21 @@ package roomescape.time.infrastructure.persistence -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import roomescape.common.entity.BaseEntity import java.time.LocalTime @Entity @Table(name = "times") class TimeEntity( @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, - var startAt: LocalTime -) + @Column(name = "time_id") + private var _id: Long?, + + @Column(name = "start_at", nullable = false) + var startAt: LocalTime, +): BaseEntity() { + override fun getId(): Long? = _id +} -- 2.47.2 From c0c2ef21c60b14ef88497b7aefe0f147f103ea26 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:55:32 +0900 Subject: [PATCH 14/25] =?UTF-8?q?refactor:=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD=20Service=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/member/business/MemberService.kt | 4 ++++ .../roomescape/payment/business/PaymentService.kt | 6 ++++++ .../reservation/business/ReservationService.kt | 4 ++++ .../persistence/ReservationSearchSpecification.kt | 6 +++--- .../roomescape/theme/business/ThemeService.kt | 15 +++++++++++++-- src/main/kotlin/roomescape/theme/web/ThemeDTO.kt | 6 ------ .../roomescape/time/business/TimeService.kt | 8 +++++++- src/main/kotlin/roomescape/time/web/TimeDTO.kt | 2 -- 8 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/roomescape/member/business/MemberService.kt b/src/main/kotlin/roomescape/member/business/MemberService.kt index 565590f7..d1a9e0ed 100644 --- a/src/main/kotlin/roomescape/member/business/MemberService.kt +++ b/src/main/kotlin/roomescape/member/business/MemberService.kt @@ -1,9 +1,11 @@ package roomescape.member.business +import com.github.f4b6a3.tsid.TsidFactory import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import roomescape.common.config.next import roomescape.member.exception.MemberErrorCode import roomescape.member.exception.MemberException import roomescape.member.infrastructure.persistence.MemberEntity @@ -16,6 +18,7 @@ private val log = KotlinLogging.logger {} @Service @Transactional(readOnly = true) class MemberService( + private val tsidFactory: TsidFactory, private val memberRepository: MemberRepository, ) { fun findMembers(): MemberRetrieveListResponse { @@ -46,6 +49,7 @@ class MemberService( } val member = MemberEntity( + _id = tsidFactory.next(), name = request.name, email = request.email, password = request.password, diff --git a/src/main/kotlin/roomescape/payment/business/PaymentService.kt b/src/main/kotlin/roomescape/payment/business/PaymentService.kt index 236bcbf3..b5458b69 100644 --- a/src/main/kotlin/roomescape/payment/business/PaymentService.kt +++ b/src/main/kotlin/roomescape/payment/business/PaymentService.kt @@ -1,8 +1,10 @@ package roomescape.payment.business +import com.github.f4b6a3.tsid.TsidFactory import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import roomescape.common.config.next import roomescape.payment.exception.PaymentErrorCode import roomescape.payment.exception.PaymentException import roomescape.payment.infrastructure.client.PaymentApproveResponse @@ -21,6 +23,7 @@ private val log = KotlinLogging.logger {} @Service class PaymentService( + private val tsidFactory: TsidFactory, private val paymentRepository: PaymentRepository, private val canceledPaymentRepository: CanceledPaymentRepository, ) { @@ -31,6 +34,7 @@ class PaymentService( ): PaymentCreateResponse { log.debug { "[PaymentService.createPayment] 결제 정보 저장 시작: request=$approveResponse, reservationId=${reservation.id}" } val payment = PaymentEntity( + _id = tsidFactory.next(), orderId = approveResponse.orderId, paymentKey = approveResponse.paymentKey, totalAmount = approveResponse.totalAmount, @@ -62,6 +66,7 @@ class PaymentService( ", cancelInfo=$cancelInfo" } val canceledPayment = CanceledPaymentEntity( + _id = tsidFactory.next(), paymentKey = paymentKey, cancelReason = cancelInfo.cancelReason, cancelAmount = cancelInfo.cancelAmount, @@ -110,6 +115,7 @@ class PaymentService( } val canceledPayment = CanceledPaymentEntity( + _id = tsidFactory.next(), paymentKey = paymentKey, cancelReason = cancelReason, cancelAmount = payment.totalAmount, diff --git a/src/main/kotlin/roomescape/reservation/business/ReservationService.kt b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt index 5f17450f..d22be309 100644 --- a/src/main/kotlin/roomescape/reservation/business/ReservationService.kt +++ b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt @@ -1,10 +1,12 @@ package roomescape.reservation.business +import com.github.f4b6a3.tsid.TsidFactory import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.data.jpa.domain.Specification import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import roomescape.common.config.next import roomescape.member.business.MemberService import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.reservation.exception.ReservationErrorCode @@ -26,6 +28,7 @@ private val log = KotlinLogging.logger {} @Service @Transactional class ReservationService( + private val tsidFactory: TsidFactory, private val reservationRepository: ReservationRepository, private val timeService: TimeService, private val memberService: MemberService, @@ -188,6 +191,7 @@ class ReservationService( validateDateAndTime(date, time) return ReservationEntity( + _id = tsidFactory.next(), date = date, time = time, theme = theme, diff --git a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt index 195f28ca..fb6d01e8 100644 --- a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt @@ -36,11 +36,11 @@ class ReservationSearchSpecification( fun confirmed(): ReservationSearchSpecification = andIfNotNull { root, _, cb -> cb.or( cb.equal( - root.get("reservationStatus"), + root.get("status"), ReservationStatus.CONFIRMED ), cb.equal( - root.get("reservationStatus"), + root.get("status"), ReservationStatus.CONFIRMED_PAYMENT_REQUIRED ) ) @@ -48,7 +48,7 @@ class ReservationSearchSpecification( fun waiting(): ReservationSearchSpecification = andIfNotNull { root, _, cb -> cb.equal( - root.get("reservationStatus"), + root.get("status"), ReservationStatus.WAITING ) } diff --git a/src/main/kotlin/roomescape/theme/business/ThemeService.kt b/src/main/kotlin/roomescape/theme/business/ThemeService.kt index ed8006c2..665fe7a2 100644 --- a/src/main/kotlin/roomescape/theme/business/ThemeService.kt +++ b/src/main/kotlin/roomescape/theme/business/ThemeService.kt @@ -1,20 +1,26 @@ package roomescape.theme.business +import com.github.f4b6a3.tsid.TsidFactory import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import roomescape.common.config.next import roomescape.theme.exception.ThemeErrorCode import roomescape.theme.exception.ThemeException import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeRepository -import roomescape.theme.web.* +import roomescape.theme.web.ThemeCreateRequest +import roomescape.theme.web.ThemeRetrieveListResponse +import roomescape.theme.web.ThemeRetrieveResponse +import roomescape.theme.web.toResponse import java.time.LocalDate private val log = KotlinLogging.logger {} @Service class ThemeService( + private val tsidFactory: TsidFactory, private val themeRepository: ThemeRepository, ) { @Transactional(readOnly = true) @@ -60,7 +66,12 @@ class ThemeService( throw ThemeException(ThemeErrorCode.THEME_NAME_DUPLICATED) } - val theme: ThemeEntity = request.toEntity() + val theme = ThemeEntity( + _id = tsidFactory.next(), + name = request.name, + description = request.description, + thumbnail = request.thumbnail + ) return themeRepository.save(theme) .also { log.info { "[ThemeService.createTheme] 테마 생성 완료: themeId=${it.id}" } } .toResponse() diff --git a/src/main/kotlin/roomescape/theme/web/ThemeDTO.kt b/src/main/kotlin/roomescape/theme/web/ThemeDTO.kt index 7ff50c62..4db87e77 100644 --- a/src/main/kotlin/roomescape/theme/web/ThemeDTO.kt +++ b/src/main/kotlin/roomescape/theme/web/ThemeDTO.kt @@ -21,12 +21,6 @@ data class ThemeCreateRequest( val thumbnail: String ) -fun ThemeCreateRequest.toEntity(): ThemeEntity = ThemeEntity( - name = this.name, - description = this.description, - thumbnail = this.thumbnail -) - data class ThemeRetrieveResponse( val id: Long, val name: String, diff --git a/src/main/kotlin/roomescape/time/business/TimeService.kt b/src/main/kotlin/roomescape/time/business/TimeService.kt index be911dc0..a49b8561 100644 --- a/src/main/kotlin/roomescape/time/business/TimeService.kt +++ b/src/main/kotlin/roomescape/time/business/TimeService.kt @@ -1,9 +1,11 @@ package roomescape.time.business +import com.github.f4b6a3.tsid.TsidFactory import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import roomescape.common.config.next import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.time.exception.TimeErrorCode @@ -18,6 +20,7 @@ private val log = KotlinLogging.logger {} @Service class TimeService( + private val tsidFactory: TsidFactory, private val timeRepository: TimeRepository, private val reservationRepository: ReservationRepository, ) { @@ -50,7 +53,10 @@ class TimeService( throw TimeException(TimeErrorCode.TIME_DUPLICATED) } - val time: TimeEntity = request.toEntity() + val time = TimeEntity( + _id = tsidFactory.next(), + startAt = request.startAt + ) return timeRepository.save(time) .also { log.info { "[TimeService.createTime] 시간 생성 완료: timeId=${it.id}" } } .toCreateResponse() diff --git a/src/main/kotlin/roomescape/time/web/TimeDTO.kt b/src/main/kotlin/roomescape/time/web/TimeDTO.kt index 8ffa146e..62ee965f 100644 --- a/src/main/kotlin/roomescape/time/web/TimeDTO.kt +++ b/src/main/kotlin/roomescape/time/web/TimeDTO.kt @@ -10,8 +10,6 @@ data class TimeCreateRequest( val startAt: LocalTime ) -fun TimeCreateRequest.toEntity(): TimeEntity = TimeEntity(startAt = this.startAt) - @Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.") data class TimeCreateResponse( @Schema(description = "시간 식별자") -- 2.47.2 From 612bbfbddc2a3780a3ecb1be2985bacb3528681b Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:56:31 +0900 Subject: [PATCH 15/25] =?UTF-8?q?test:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/business/AuthServiceTest.kt | 3 +- .../payment/business/PaymentServiceTest.kt | 3 +- .../client/TossPaymentClientTest.kt | 3 + .../CanceledPaymentRepositoryTest.kt | 6 +- .../persistence/PaymentRepositoryTest.kt | 8 +- .../business/ReservationServiceTest.kt | 2 + .../persistence/ReservationRepositoryTest.kt | 2 +- .../ReservationSearchSpecificationTest.kt | 2 +- .../web/ReservationControllerTest.kt | 471 ++++++++---------- .../theme/business/ThemeServiceTest.kt | 3 +- .../persistence/ThemeRepositoryTest.kt | 2 +- .../theme/web/ThemeControllerTest.kt | 1 + .../time/business/TimeServiceTest.kt | 2 + .../persistence/TimeRepositoryTest.kt | 2 +- .../roomescape/time/web/TimeControllerTest.kt | 2 +- src/test/kotlin/roomescape/util/Fixtures.kt | 21 +- .../roomescape/util/RoomescapeApiTest.kt | 15 + 17 files changed, 259 insertions(+), 289 deletions(-) diff --git a/src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt b/src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt index 8d629510..aa570047 100644 --- a/src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt +++ b/src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt @@ -15,10 +15,11 @@ import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberRepository import roomescape.util.JwtFixture import roomescape.util.MemberFixture +import roomescape.util.TsidFactory class AuthServiceTest : BehaviorSpec({ val memberRepository: MemberRepository = mockk() - val memberService: MemberService = MemberService(memberRepository) + val memberService = MemberService(TsidFactory, memberRepository) val jwtHandler: JwtHandler = JwtFixture.create() val authService = AuthService(memberService, jwtHandler) diff --git a/src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt b/src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt index 7b5f2e36..71d9696d 100644 --- a/src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt +++ b/src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt @@ -14,13 +14,14 @@ import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository import roomescape.payment.infrastructure.persistence.PaymentRepository import roomescape.payment.web.PaymentCancelRequest import roomescape.util.PaymentFixture +import roomescape.util.TsidFactory import java.time.OffsetDateTime class PaymentServiceTest : FunSpec({ val paymentRepository: PaymentRepository = mockk() val canceledPaymentRepository: CanceledPaymentRepository = mockk() - val paymentService = PaymentService(paymentRepository, canceledPaymentRepository) + val paymentService = PaymentService(TsidFactory, paymentRepository, canceledPaymentRepository) context("createCanceledPaymentByReservationId") { val reservationId = 1L diff --git a/src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt index f5984ddd..eb8906ad 100644 --- a/src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt @@ -1,11 +1,13 @@ package roomescape.payment.infrastructure.client +import com.ninjasquad.springmockk.MockkBean import io.kotest.assertions.assertSoftly import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.client.RestClientTest +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.http.MediaType @@ -20,6 +22,7 @@ import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelResponse @RestClientTest(TossPaymentClient::class) +@MockkBean(JpaMetamodelMappingContext::class) class TossPaymentClientTest( @Autowired val client: TossPaymentClient, @Autowired val mockServer: MockRestServiceServer diff --git a/src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt index f08657bb..052d1eb8 100644 --- a/src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt @@ -5,10 +5,12 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import roomescape.common.config.next import roomescape.util.PaymentFixture +import roomescape.util.TsidFactory import java.util.* -@DataJpaTest +@DataJpaTest(showSql = false) class CanceledPaymentRepositoryTest( @Autowired val canceledPaymentRepository: CanceledPaymentRepository, ) : FunSpec() { @@ -16,7 +18,7 @@ class CanceledPaymentRepositoryTest( context("paymentKey로 CanceledPaymentEntity 조회") { val paymentKey = "test-payment-key" beforeTest { - PaymentFixture.createCanceled(paymentKey = paymentKey) + PaymentFixture.createCanceled(id = TsidFactory.next(), paymentKey = paymentKey) .also { canceledPaymentRepository.save(it) } } diff --git a/src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt index 02e5c165..51979b53 100644 --- a/src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt @@ -6,11 +6,13 @@ import io.kotest.matchers.shouldBe import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import roomescape.common.config.next import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.util.PaymentFixture import roomescape.util.ReservationFixture +import roomescape.util.TsidFactory -@DataJpaTest +@DataJpaTest(showSql = false) class PaymentRepositoryTest( @Autowired val paymentRepository: PaymentRepository, @Autowired val entityManager: EntityManager @@ -91,7 +93,9 @@ class PaymentRepositoryTest( } private fun setupReservation(): ReservationEntity { - return ReservationFixture.create().also { + return ReservationFixture.create( + id = TsidFactory.next() + ).also { entityManager.persist(it.member) entityManager.persist(it.theme) entityManager.persist(it.time) diff --git a/src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt b/src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt index 95f5b118..54c6cccf 100644 --- a/src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt +++ b/src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt @@ -16,6 +16,7 @@ import roomescape.theme.business.ThemeService import roomescape.time.business.TimeService import roomescape.util.MemberFixture import roomescape.util.ReservationFixture +import roomescape.util.TsidFactory import roomescape.util.TimeFixture import java.time.LocalDate import java.time.LocalTime @@ -27,6 +28,7 @@ class ReservationServiceTest : FunSpec({ val memberService: MemberService = mockk() val themeService: ThemeService = mockk() val reservationService = ReservationService( + TsidFactory, reservationRepository, timeService, memberService, diff --git a/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt index 055ed019..78d44d3f 100644 --- a/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt +++ b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt @@ -15,7 +15,7 @@ import roomescape.util.ReservationFixture import roomescape.util.ThemeFixture import roomescape.util.TimeFixture -@DataJpaTest +@DataJpaTest(showSql = false) class ReservationRepositoryTest( val entityManager: EntityManager, val reservationRepository: ReservationRepository, diff --git a/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt index 130b062b..88323f85 100644 --- a/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt +++ b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt @@ -15,7 +15,7 @@ import roomescape.util.ThemeFixture import roomescape.util.TimeFixture import java.time.LocalDate -@DataJpaTest +@DataJpaTest(showSql = false) class ReservationSearchSpecificationTest( val entityManager: EntityManager, val reservationRepository: ReservationRepository diff --git a/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt index 0ebaad3b..a5fdf245 100644 --- a/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt +++ b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt @@ -39,7 +39,7 @@ import java.time.LocalTime class ReservationControllerTest( @LocalServerPort val port: Int, val entityManager: EntityManager, - val transactionTemplate: TransactionTemplate + val transactionTemplate: TransactionTemplate, ) : FunSpec({ extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_EACH_TEST)) }) { @@ -55,24 +55,34 @@ class ReservationControllerTest( @MockkBean lateinit var jwtHandler: JwtHandler + lateinit var testDataHelper: TestDataHelper + + fun login(member: MemberEntity) { + every { jwtHandler.getMemberIdFromToken(any()) } returns member.id!! + every { memberService.findById(member.id!!) } returns member + every { memberIdResolver.resolveArgument(any(), any(), any(), any()) } returns member.id!! + } + init { + beforeSpec { + testDataHelper = TestDataHelper(entityManager, transactionTemplate) + } + context("POST /reservations") { - lateinit var member: MemberEntity beforeTest { - member = login(MemberFixture.create(role = Role.MEMBER)) + val member = testDataHelper.createMember(role = Role.MEMBER) + login(member) } test("정상 응답") { - val reservationRequest = createRequest() + val reservationRequest = testDataHelper.createReservationRequest() val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( paymentKey = reservationRequest.paymentKey, orderId = reservationRequest.orderId, totalAmount = reservationRequest.amount, ) - every { - paymentClient.confirm(any()) - } returns paymentApproveResponse + every { paymentClient.confirm(any()) } returns paymentApproveResponse Given { port(port) @@ -88,12 +98,10 @@ class ReservationControllerTest( } test("결제 과정에서 발생하는 에러는 그대로 응답") { - val reservationRequest = createRequest() + val reservationRequest = testDataHelper.createReservationRequest() val paymentException = PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR) - every { - paymentClient.confirm(any()) - } throws paymentException + every { paymentClient.confirm(any()) } throws paymentException Given { port(port) @@ -108,24 +116,20 @@ class ReservationControllerTest( } test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답을 받는다.") { - val reservationRequest = createRequest() + val reservationRequest = testDataHelper.createReservationRequest() val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( paymentKey = reservationRequest.paymentKey, orderId = reservationRequest.orderId, totalAmount = reservationRequest.amount, ) - every { - paymentClient.confirm(any()) - } returns paymentApproveResponse + every { paymentClient.confirm(any()) } returns paymentApproveResponse // 예약 저장 과정에서 테마가 없는 예외 val invalidRequest = reservationRequest.copy(themeId = reservationRequest.themeId + 1) val expectedException = ThemeErrorCode.THEME_NOT_FOUND - every { - paymentClient.cancel(any()) - } returns PaymentFixture.createCancelResponse() + every { paymentClient.cancel(any()) } returns PaymentFixture.createCancelResponse() val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery( "SELECT COUNT(c) FROM CanceledPaymentEntity c", @@ -153,13 +157,13 @@ class ReservationControllerTest( } context("GET /reservations") { - lateinit var reservations: MutableMap> + lateinit var reservations: Map> beforeTest { - reservations = createDummyReservations() + reservations = testDataHelper.createDummyReservations() } test("관리자이면 정상 응답") { - login(MemberFixture.create(role = Role.ADMIN)) + login(testDataHelper.createMember(role = Role.ADMIN)) Given { port(port) contentType(MediaType.APPLICATION_JSON_VALUE) @@ -173,13 +177,14 @@ class ReservationControllerTest( } context("GET /reservations-mine") { - lateinit var reservations: MutableMap> + lateinit var reservations: Map> beforeTest { - reservations = createDummyReservations() + reservations = testDataHelper.createDummyReservations() } test("로그인한 회원이 자신의 예약 목록을 조회한다.") { - val member: MemberEntity = login(reservations.keys.first()) + val member = reservations.keys.first() + login(member) val expectedReservations: Int = reservations[member]?.size ?: 0 Given { @@ -195,9 +200,9 @@ class ReservationControllerTest( } context("GET /reservations/search") { - lateinit var reservations: MutableMap> + lateinit var reservations: Map> beforeTest { - reservations = createDummyReservations() + reservations = testDataHelper.createDummyReservations() } test("관리자만 검색할 수 있다.") { @@ -216,7 +221,7 @@ class ReservationControllerTest( } test("파라미터를 지정하지 않으면 전체 목록 응답") { - login(MemberFixture.create(role = Role.ADMIN)) + login(testDataHelper.createMember(role = Role.ADMIN)) Given { port(port) @@ -230,7 +235,7 @@ class ReservationControllerTest( } test("시작 날짜가 종료 날짜 이전이면 예외 응답") { - login(MemberFixture.create(role = Role.ADMIN)) + login(testDataHelper.createMember(role = Role.ADMIN)) val startDate = LocalDate.now().plusDays(1) val endDate = LocalDate.now() @@ -250,8 +255,8 @@ class ReservationControllerTest( } test("동일한 회원의 모든 예약 응답") { - login(MemberFixture.create(role = Role.ADMIN)) - val member: MemberEntity = reservations.keys.first() + login(testDataHelper.createMember(role = Role.ADMIN)) + val member = reservations.keys.first() Given { port(port) @@ -266,7 +271,7 @@ class ReservationControllerTest( } test("동일한 테마의 모든 예약 응답") { - login(MemberFixture.create(role = Role.ADMIN)) + login(testDataHelper.createMember(role = Role.ADMIN)) val themes = reservations.values.flatten().map { it.theme } val requestThemeId: Long = themes.first().id!! @@ -278,12 +283,12 @@ class ReservationControllerTest( get("/reservations/search") }.Then { statusCode(200) - body("data.reservations.size()", equalTo(themes.filter { it.id == requestThemeId }.size)) + body("data.reservations.size()", equalTo(themes.count { it.id == requestThemeId })) } } test("시작 날짜와 종료 날짜 사이의 예약 응답") { - login(MemberFixture.create(role = Role.ADMIN)) + login(testDataHelper.createMember(role = Role.ADMIN)) val dateFrom: LocalDate = reservations.values.flatten().minOf { it.date } val dateTo: LocalDate = reservations.values.flatten().maxOf { it.date } @@ -302,14 +307,14 @@ class ReservationControllerTest( } context("DELETE /reservations/{id}") { - lateinit var reservations: MutableMap> + lateinit var reservations: Map> beforeTest { - reservations = createDummyReservations() + reservations = testDataHelper.createDummyReservations() } test("관리자만 예약을 삭제할 수 있다.") { - login(MemberFixture.create(role = Role.MEMBER)) - val reservation: ReservationEntity = reservations.values.flatten().first() + login(testDataHelper.createMember(role = Role.MEMBER)) + val reservation = reservations.values.flatten().first() val expectedError = AuthErrorCode.ACCESS_DENIED Given { @@ -323,18 +328,12 @@ class ReservationControllerTest( } test("결제되지 않은 예약은 바로 제거") { - login(MemberFixture.create(role = Role.ADMIN)) - val reservationId: Long = reservations.values.flatten().first().id!! + login(testDataHelper.createMember(role = Role.ADMIN)) + val reservationId = reservations.values.flatten().first().id!! - transactionTemplate.execute { - val reservation: ReservationEntity = entityManager.find( - ReservationEntity::class.java, - reservationId - ) + transactionTemplate.executeWithoutResult { + val reservation = entityManager.find(ReservationEntity::class.java, reservationId) reservation.status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED - entityManager.persist(reservation) - entityManager.flush() - entityManager.clear() } Given { @@ -345,32 +344,18 @@ class ReservationControllerTest( statusCode(HttpStatus.NO_CONTENT.value()) } - // 예약이 삭제되었는지 확인 - transactionTemplate.executeWithoutResult { - val deletedReservation = entityManager.find( - ReservationEntity::class.java, - reservationId - ) - deletedReservation shouldBe null + val deletedReservation = transactionTemplate.execute { + entityManager.find(ReservationEntity::class.java, reservationId) } + deletedReservation shouldBe null } test("결제된 예약은 취소 후 제거") { - login(MemberFixture.create(role = Role.ADMIN)) - val reservation: ReservationEntity = reservations.values.flatten().first() - lateinit var payment: PaymentEntity + login(testDataHelper.createMember(role = Role.ADMIN)) + val reservation = reservations.values.flatten().first { it.status == ReservationStatus.CONFIRMED } + testDataHelper.createPayment(reservation) - transactionTemplate.execute { - payment = PaymentFixture.create(reservation = reservation).also { - entityManager.persist(it) - entityManager.flush() - entityManager.clear() - } - } - - every { - paymentClient.cancel(any()) - } returns PaymentFixture.createCancelResponse() + every { paymentClient.cancel(any()) } returns PaymentFixture.createCancelResponse() val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery( "SELECT COUNT(c) FROM CanceledPaymentEntity c", @@ -396,15 +381,17 @@ class ReservationControllerTest( context("POST /reservations/admin") { test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") { - val member = login(MemberFixture.create(role = Role.ADMIN)) - val adminRequest: AdminReservationCreateRequest = createRequest().let { - AdminReservationCreateRequest( - date = it.date, - themeId = it.themeId, - timeId = it.timeId, - memberId = member.id!!, - ) - } + val admin = testDataHelper.createMember(role = Role.ADMIN) + login(admin) + val theme = testDataHelper.createTheme() + val time = testDataHelper.createTime() + + val adminRequest = AdminReservationCreateRequest( + date = LocalDate.now().plusDays(1), + themeId = theme.id!!, + timeId = time.id!!, + memberId = admin.id!!, + ) Given { port(port) @@ -420,13 +407,13 @@ class ReservationControllerTest( } context("GET /reservations/waiting") { - lateinit var reservations: MutableMap> + lateinit var reservations: Map> beforeTest { - reservations = createDummyReservations() + reservations = testDataHelper.createDummyReservations(reservationCount = 5) } test("관리자가 아니면 조회할 수 없다.") { - login(MemberFixture.create(role = Role.MEMBER)) + login(testDataHelper.createMember(role = Role.MEMBER)) val expectedError = AuthErrorCode.ACCESS_DENIED Given { @@ -441,7 +428,7 @@ class ReservationControllerTest( } test("대기 중인 예약 목록을 조회한다.") { - login(MemberFixture.create(role = Role.ADMIN)) + login(testDataHelper.createMember(role = Role.ADMIN)) val expected = reservations.values.flatten() .count { it.status == ReservationStatus.WAITING } @@ -459,14 +446,16 @@ class ReservationControllerTest( context("POST /reservations/waiting") { test("회원이 대기 예약을 추가한다.") { - val member = login(MemberFixture.create(role = Role.MEMBER)) - val waitingCreateRequest: WaitingCreateRequest = createRequest().let { - WaitingCreateRequest( - date = it.date, - themeId = it.themeId, - timeId = it.timeId - ) - } + val member = testDataHelper.createMember(role = Role.MEMBER) + login(member) + val theme = testDataHelper.createTheme() + val time = testDataHelper.createTime() + + val waitingCreateRequest = WaitingCreateRequest( + date = LocalDate.now().plusDays(1), + themeId = theme.id!!, + timeId = time.id!! + ) Given { port(port) @@ -476,33 +465,30 @@ class ReservationControllerTest( post("/reservations/waiting") }.Then { statusCode(201) - body("data.member.id", equalTo(member.id!!.toInt())) + body("data.member.id", equalTo(member.id!!)) body("data.status", equalTo(ReservationStatus.WAITING.name)) } } test("이미 예약된 시간, 테마로 대기 예약 요청 시 예외 응답") { - val member = login(MemberFixture.create(role = Role.MEMBER)) - val reservationRequest = createRequest() + val member = testDataHelper.createMember(role = Role.MEMBER) + login(member) + val theme = testDataHelper.createTheme() + val time = testDataHelper.createTime() + val date = LocalDate.now().plusDays(1) - transactionTemplate.executeWithoutResult { - val reservation = ReservationFixture.create( - date = reservationRequest.date, - theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId), - time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId), - member = member, - status = ReservationStatus.WAITING - ) - entityManager.persist(reservation) - entityManager.flush() - entityManager.clear() - } + testDataHelper.createReservation( + date = date, + theme = theme, + time = time, + member = member, + status = ReservationStatus.CONFIRMED + ) - // 이미 예약된 시간, 테마로 대기 예약 요청 val waitingCreateRequest = WaitingCreateRequest( - date = reservationRequest.date, - themeId = reservationRequest.themeId, - timeId = reservationRequest.timeId + date = date, + themeId = theme.id!!, + timeId = time.id!! ) val expectedError = ReservationErrorCode.ALREADY_RESERVE @@ -520,14 +506,10 @@ class ReservationControllerTest( } context("DELETE /reservations/waiting/{id}") { - lateinit var reservations: MutableMap> - beforeTest { - reservations = createDummyReservations() - } - test("대기 중인 예약을 취소한다.") { - val member = login(MemberFixture.create(role = Role.MEMBER)) - val waiting: ReservationEntity = createSingleReservation( + val member = testDataHelper.createMember(role = Role.MEMBER) + login(member) + val waiting = testDataHelper.createReservation( member = member, status = ReservationStatus.WAITING ) @@ -540,17 +522,16 @@ class ReservationControllerTest( statusCode(HttpStatus.NO_CONTENT.value()) } - transactionTemplate.executeWithoutResult { _ -> - entityManager.find( - ReservationEntity::class.java, - waiting.id - ) shouldBe null + val deleted = transactionTemplate.execute { + entityManager.find(ReservationEntity::class.java, waiting.id) } + deleted shouldBe null } test("이미 확정된 예약을 삭제하면 예외 응답") { - val member = login(MemberFixture.create(role = Role.MEMBER)) - val reservation: ReservationEntity = createSingleReservation( + val member = testDataHelper.createMember(role = Role.MEMBER) + login(member) + val reservation = testDataHelper.createReservation( member = member, status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED ) @@ -559,7 +540,7 @@ class ReservationControllerTest( Given { port(port) }.When { - delete("/reservations/waiting/{id}", reservation.id) + delete("/reservations/waiting/${reservation.id}") }.Then { statusCode(expectedError.httpStatus.value()) body("code", equalTo(expectedError.errorCode)) @@ -569,7 +550,7 @@ class ReservationControllerTest( context("POST /reservations/waiting/{id}/confirm") { test("관리자만 승인할 수 있다.") { - login(MemberFixture.create(role = Role.MEMBER)) + login(testDataHelper.createMember(role = Role.MEMBER)) val expectedError = AuthErrorCode.ACCESS_DENIED Given { port(port) @@ -582,9 +563,8 @@ class ReservationControllerTest( } test("대기 예약을 승인하면 결제 대기 상태로 변경") { - val member = login(MemberFixture.create(role = Role.ADMIN)) - val reservation = createSingleReservation( - member = member, + login(testDataHelper.createMember(role = Role.ADMIN)) + val reservation = testDataHelper.createReservation( status = ReservationStatus.WAITING ) @@ -596,39 +576,28 @@ class ReservationControllerTest( statusCode(200) } - transactionTemplate.executeWithoutResult { _ -> - entityManager.find( - ReservationEntity::class.java, - reservation.id - )?.also { - it.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED - } ?: throw AssertionError("Reservation not found") + val updatedReservation = transactionTemplate.execute { + entityManager.find(ReservationEntity::class.java, reservation.id) } + updatedReservation?.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED } test("다른 확정된 예약을 승인하면 예외 응답") { - val admin = login(MemberFixture.create(role = Role.ADMIN)) - val alreadyReserved = createSingleReservation( + val admin = testDataHelper.createMember(role = Role.ADMIN) + login(admin) + val alreadyReserved = testDataHelper.createReservation( member = admin, status = ReservationStatus.CONFIRMED ) - val member = MemberFixture.create(account = "account", role = Role.MEMBER).also { it -> - transactionTemplate.executeWithoutResult { _ -> - entityManager.persist(it) - } - } - val waiting = ReservationFixture.create( + val member = testDataHelper.createMember(role = Role.MEMBER) + val waiting = testDataHelper.createReservation( date = alreadyReserved.date, time = alreadyReserved.time, theme = alreadyReserved.theme, member = member, status = ReservationStatus.WAITING - ).also { - transactionTemplate.executeWithoutResult { _ -> - entityManager.persist(it) - } - } + ) val expectedError = ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS Given { @@ -636,7 +605,6 @@ class ReservationControllerTest( }.When { post("/reservations/waiting/${waiting.id!!}/confirm") }.Then { - log().all() statusCode(expectedError.httpStatus.value()) body("code", equalTo(expectedError.errorCode)) } @@ -645,7 +613,7 @@ class ReservationControllerTest( context("POST /reservations/waiting/{id}/reject") { test("관리자만 거절할 수 있다.") { - login(MemberFixture.create(role = Role.MEMBER)) + login(testDataHelper.createMember(role = Role.MEMBER)) val expectedError = AuthErrorCode.ACCESS_DENIED Given { @@ -659,9 +627,8 @@ class ReservationControllerTest( } test("거절된 예약은 삭제된다.") { - val member = login(MemberFixture.create(role = Role.ADMIN)) - val reservation = createSingleReservation( - member = member, + login(testDataHelper.createMember(role = Role.ADMIN)) + val reservation = testDataHelper.createReservation( status = ReservationStatus.WAITING ) @@ -673,125 +640,91 @@ class ReservationControllerTest( statusCode(204) } - transactionTemplate.executeWithoutResult { _ -> - entityManager.find( - ReservationEntity::class.java, - reservation.id - ) shouldBe null + val rejected = transactionTemplate.execute { + entityManager.find(ReservationEntity::class.java, reservation.id) } + rejected shouldBe null } } } - - fun createSingleReservation( - date: LocalDate = LocalDate.now().plusDays(1), - time: LocalTime = LocalTime.now(), - themeName: String = "Default Theme", - member: MemberEntity = MemberFixture.create(role = Role.MEMBER), - status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED - ): ReservationEntity { - return ReservationFixture.create( - date = date, - theme = ThemeFixture.create(name = themeName), - time = TimeFixture.create(startAt = time), - member = member, - status = status - ).also { it -> - transactionTemplate.execute { _ -> - if (member.id == null) { - entityManager.persist(member) - } - entityManager.persist(it.time) - entityManager.persist(it.theme) - entityManager.persist(it) - entityManager.flush() - entityManager.clear() - } - } - } - - fun createDummyReservations(): MutableMap> { - val reservations: MutableMap> = mutableMapOf() - val members: List = listOf( - MemberFixture.create(role = Role.MEMBER), - MemberFixture.create(role = Role.MEMBER) - ) - - transactionTemplate.executeWithoutResult { - members.forEach { member -> - entityManager.persist(member) - } - entityManager.flush() - entityManager.clear() - } - - transactionTemplate.executeWithoutResult { - repeat(10) { index -> - val theme = ThemeFixture.create(name = "theme$index") - val time = TimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong())) - entityManager.persist(theme) - entityManager.persist(time) - - val reservation = ReservationFixture.create( - date = LocalDate.now().plusDays(index.toLong()), - theme = theme, - time = time, - member = members[index % members.size], - status = ReservationStatus.CONFIRMED - ) - entityManager.persist(reservation) - reservations.getOrPut(reservation.member) { mutableListOf() }.add(reservation) - } - entityManager.flush() - entityManager.clear() - } - - return reservations - } - - fun createRequest( - theme: ThemeEntity = ThemeFixture.create(), - time: TimeEntity = TimeFixture.create(), - ): ReservationCreateWithPaymentRequest { - lateinit var reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest - - transactionTemplate.executeWithoutResult { - entityManager.persist(theme) - entityManager.persist(time) - - reservationCreateWithPaymentRequest = ReservationFixture.createRequest( - themeId = theme.id!!, - timeId = time.id!!, - ) - - entityManager.flush() - entityManager.clear() - } - - return reservationCreateWithPaymentRequest - } - - fun login(member: MemberEntity): MemberEntity { - if (member.id == null) { - transactionTemplate.executeWithoutResult { - entityManager.persist(member) - entityManager.flush() - entityManager.clear() - } - } - - every { - jwtHandler.getMemberIdFromToken(any()) - } returns member.id!! - - every { - memberService.findById(member.id!!) - } returns member - - every { - memberIdResolver.resolveArgument(any(), any(), any(), any()) - } returns member.id!! - - return member - } } + +class TestDataHelper( + private val entityManager: EntityManager, + private val transactionTemplate: TransactionTemplate, +) { + private var memberSequence = 0L + private var themeSequence = 0L + private var timeSequence = 0L + + fun createMember( + role: Role = Role.MEMBER, + account: String = "member${++memberSequence}@test.com", + ): MemberEntity { + val member = MemberFixture.create(role = role, account = account) + return persist(member) + } + + fun createTheme(name: String = "theme-${++themeSequence}"): ThemeEntity { + val theme = ThemeFixture.create(name = name) + return persist(theme) + } + + fun createTime(startAt: LocalTime = LocalTime.of(10, 0).plusMinutes(++timeSequence * 10)): TimeEntity { + val time = TimeFixture.create(startAt = startAt) + return persist(time) + } + + fun createReservation( + date: LocalDate = LocalDate.now().plusDays(1), + theme: ThemeEntity = createTheme(), + time: TimeEntity = createTime(), + member: MemberEntity = createMember(), + status: ReservationStatus = ReservationStatus.CONFIRMED, + ): ReservationEntity { + val reservation = ReservationFixture.create( + date = date, + theme = theme, + time = time, + member = member, + status = status + ) + return persist(reservation) + } + + fun createPayment(reservation: ReservationEntity): PaymentEntity { + val payment = PaymentFixture.create(reservation = reservation) + return persist(payment) + } + + fun createReservationRequest( + theme: ThemeEntity = createTheme(), + time: TimeEntity = createTime(), + ): ReservationCreateWithPaymentRequest { + return ReservationFixture.createRequest( + themeId = theme.id!!, + timeId = time.id!!, + ) + } + + fun createDummyReservations( + memberCount: Int = 2, + reservationCount: Int = 10, + ): Map> { + val members = (1..memberCount).map { createMember(role = Role.MEMBER) } + val reservations = (1..reservationCount).map { index -> + createReservation( + member = members[index % memberCount], + status = ReservationStatus.CONFIRMED + ) + } + return reservations.groupBy { it.member } + } + + private fun persist(entity: T): T { + transactionTemplate.executeWithoutResult { + entityManager.persist(entity) + } + return entity + } +} \ No newline at end of file diff --git a/src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt b/src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt index 80a534e7..4cecc9d1 100644 --- a/src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt +++ b/src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt @@ -13,12 +13,13 @@ import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeRepository import roomescape.theme.web.ThemeCreateRequest import roomescape.theme.web.ThemeRetrieveResponse +import roomescape.util.TsidFactory import roomescape.util.ThemeFixture class ThemeServiceTest : FunSpec({ val themeRepository: ThemeRepository = mockk() - val themeService = ThemeService(themeRepository) + val themeService = ThemeService(TsidFactory, themeRepository) context("findThemeById") { val themeId = 1L diff --git a/src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt b/src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt index bc7d35de..2784f4ea 100644 --- a/src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt +++ b/src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt @@ -8,7 +8,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import roomescape.theme.util.TestThemeCreateUtil import java.time.LocalDate -@DataJpaTest +@DataJpaTest(showSql = false) class ThemeRepositoryTest( val themeRepository: ThemeRepository, val entityManager: EntityManager diff --git a/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt b/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt index 7a6a130d..a6cc1dff 100644 --- a/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt +++ b/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt @@ -10,6 +10,7 @@ import io.mockk.just import io.mockk.runs import org.hamcrest.Matchers.equalTo import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import roomescape.auth.exception.AuthErrorCode diff --git a/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt b/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt index 125976bf..73266aaf 100644 --- a/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt +++ b/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt @@ -14,6 +14,7 @@ import roomescape.time.exception.TimeErrorCode import roomescape.time.exception.TimeException import roomescape.time.infrastructure.persistence.TimeRepository import roomescape.time.web.TimeCreateRequest +import roomescape.util.TsidFactory import roomescape.util.TimeFixture import java.time.LocalTime @@ -22,6 +23,7 @@ class TimeServiceTest : FunSpec({ val reservationRepository: ReservationRepository = mockk() val timeService = TimeService( + tsidFactory = TsidFactory, timeRepository = timeRepository, reservationRepository = reservationRepository ) diff --git a/src/test/kotlin/roomescape/time/infrastructure/persistence/TimeRepositoryTest.kt b/src/test/kotlin/roomescape/time/infrastructure/persistence/TimeRepositoryTest.kt index 5c99b07d..bc79357b 100644 --- a/src/test/kotlin/roomescape/time/infrastructure/persistence/TimeRepositoryTest.kt +++ b/src/test/kotlin/roomescape/time/infrastructure/persistence/TimeRepositoryTest.kt @@ -7,7 +7,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import roomescape.util.TimeFixture import java.time.LocalTime -@DataJpaTest +@DataJpaTest(showSql = false) class TimeRepositoryTest( val entityManager: EntityManager, val timeRepository: TimeRepository, diff --git a/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt b/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt index b9ef49a9..c5a57e44 100644 --- a/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt +++ b/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt @@ -9,6 +9,7 @@ import io.mockk.every import org.hamcrest.Matchers.equalTo import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.annotation.Import +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext import org.springframework.data.repository.findByIdOrNull import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc @@ -27,7 +28,6 @@ import java.time.LocalDate import java.time.LocalTime @WebMvcTest(TimeController::class) -@Import(JacksonConfig::class) class TimeControllerTest( val mockMvc: MockMvc, ) : RoomescapeApiTest() { diff --git a/src/test/kotlin/roomescape/util/Fixtures.kt b/src/test/kotlin/roomescape/util/Fixtures.kt index 43fa2824..526e3d16 100644 --- a/src/test/kotlin/roomescape/util/Fixtures.kt +++ b/src/test/kotlin/roomescape/util/Fixtures.kt @@ -1,7 +1,9 @@ package roomescape.util +import com.github.f4b6a3.tsid.TsidFactory import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.web.LoginRequest +import roomescape.common.config.next import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.Role import roomescape.payment.infrastructure.client.PaymentApproveRequest @@ -20,11 +22,14 @@ import java.time.LocalDate import java.time.LocalTime import java.time.OffsetDateTime + +val TsidFactory: TsidFactory = TsidFactory(0) + object MemberFixture { const val NOT_LOGGED_IN_USERID: Long = 0 fun create( - id: Long? = null, + id: Long? = TsidFactory.next(), name: String = "sangdol", account: String = "default", password: String = "password", @@ -56,14 +61,14 @@ object MemberFixture { object TimeFixture { fun create( - id: Long? = null, + id: Long? = TsidFactory.next(), startAt: LocalTime = LocalTime.now().plusHours(1), ): TimeEntity = TimeEntity(id, startAt) } object ThemeFixture { fun create( - id: Long? = null, + id: Long? = TsidFactory.next(), name: String = "Default Theme", description: String = "Default Description", thumbnail: String = "https://example.com/default-thumbnail.jpg" @@ -72,7 +77,7 @@ object ThemeFixture { object ReservationFixture { fun create( - id: Long? = null, + id: Long? = TsidFactory.next(), date: LocalDate = LocalDate.now().plusWeeks(1), theme: ThemeEntity = ThemeFixture.create(), time: TimeEntity = TimeFixture.create(), @@ -125,14 +130,14 @@ object PaymentFixture { const val AMOUNT: Long = 10000L fun create( - id: Long? = null, + id: Long? = TsidFactory.next(), orderId: String = ORDER_ID, paymentKey: String = PAYMENT_KEY, totalAmount: Long = AMOUNT, reservation: ReservationEntity = ReservationFixture.create(id = 1L), approvedAt: OffsetDateTime = OffsetDateTime.now() ): PaymentEntity = PaymentEntity( - id = id, + _id = id, orderId = orderId, paymentKey = paymentKey, totalAmount = totalAmount, @@ -141,14 +146,14 @@ object PaymentFixture { ) fun createCanceled( - id: Long? = null, + id: Long? = TsidFactory.next(), paymentKey: String = PAYMENT_KEY, cancelReason: String = "Test Cancel", cancelAmount: Long = AMOUNT, approvedAt: OffsetDateTime = OffsetDateTime.now(), canceledAt: OffsetDateTime = approvedAt.plusHours(1) ): CanceledPaymentEntity = CanceledPaymentEntity( - id = id, + _id = id, paymentKey = paymentKey, cancelReason = cancelReason, cancelAmount = cancelAmount, diff --git a/src/test/kotlin/roomescape/util/RoomescapeApiTest.kt b/src/test/kotlin/roomescape/util/RoomescapeApiTest.kt index f5e40349..9a038941 100644 --- a/src/test/kotlin/roomescape/util/RoomescapeApiTest.kt +++ b/src/test/kotlin/roomescape/util/RoomescapeApiTest.kt @@ -1,10 +1,16 @@ package roomescape.util import com.fasterxml.jackson.databind.ObjectMapper +import com.github.f4b6a3.tsid.TsidFactory import com.ninjasquad.springmockk.MockkBean import com.ninjasquad.springmockk.SpykBean import io.kotest.core.spec.style.BehaviorSpec import io.mockk.every +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Import +import org.springframework.context.annotation.Primary +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext import org.springframework.data.repository.findByIdOrNull import org.springframework.http.HttpHeaders import org.springframework.http.MediaType @@ -21,6 +27,8 @@ import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberRepository import roomescape.util.MemberFixture.NOT_LOGGED_IN_USERID +@Import(TestConfig::class, JacksonConfig::class) +@MockkBean(JpaMetamodelMappingContext::class) abstract class RoomescapeApiTest : BehaviorSpec() { @SpykBean @@ -128,3 +136,10 @@ abstract class RoomescapeApiTest : BehaviorSpec() { """.trimIndent() ) } + +@TestConfiguration +class TestConfig { + @Bean + @Primary + fun tsidFactory(): TsidFactory = TsidFactory +} \ No newline at end of file -- 2.47.2 From 3f5af938170a5f4839e138e11abd5679479163a4 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:57:32 +0900 Subject: [PATCH 16/25] =?UTF-8?q?feat:=202af09231=20=EC=BB=A4=EB=B0=8B=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/roomescape/common/log/ProxyDataSourceConfig.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt b/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt index 4038b3fd..b0300f54 100644 --- a/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt +++ b/src/main/kotlin/roomescape/common/log/ProxyDataSourceConfig.kt @@ -1,6 +1,7 @@ package roomescape.common.log import com.zaxxer.hikari.HikariDataSource +import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder import org.springframework.beans.factory.annotation.Qualifier import org.springframework.boot.context.properties.ConfigurationProperties @@ -26,7 +27,8 @@ class ProxyDataSourceConfig { .name(properties.loggerName) .listener( MDCAwareSlowQueryListenerWithoutParams( - properties.thresholdMs + logLevel = SLF4JLogLevel.nullSafeValueOf(properties.logLevel.uppercase()), + thresholdMs = properties.thresholdMs ) ) .buildProxy() @@ -42,5 +44,6 @@ class ProxyDataSourceConfig { @ConfigurationProperties(prefix = "slow-query") data class SlowQueryProperties( val loggerName: String, + val logLevel: String, val thresholdMs: Long, ) -- 2.47.2 From 992ac4232cc52ab96f87602d314e86d4b955e8e6 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 15:58:35 +0900 Subject: [PATCH 17/25] =?UTF-8?q?feat:=20=EB=B0=B0=ED=8F=AC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=9A=A9=20application-deploy=20=EB=B0=8F=20Dockerfil?= =?UTF-8?q?e=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 10 +++++++ src/main/resources/application-deploy.yaml | 35 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 Dockerfile create mode 100644 src/main/resources/application-deploy.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..9bbe253d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM gradle:8-jdk17 AS builder +WORKDIR /app +COPY . . +RUN gradle bootJar --no-daemon + +FROM amazoncorretto:17 +WORKDIR /app +EXPOSE 8080 +COPY --from=builder /app/build/libs/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/src/main/resources/application-deploy.yaml b/src/main/resources/application-deploy.yaml new file mode 100644 index 00000000..e390837e --- /dev/null +++ b/src/main/resources/application-deploy.yaml @@ -0,0 +1,35 @@ +spring: + sql: + init: + schema-locations: classpath:schema/schema-mysql.sql + jpa: + defer-datasource-initialization: false + hibernate: + ddl-auto: validate + datasource: + hikari: + driver-class-name: ${DATASOURCE_DRIVER_CLASS_NAME} + jdbc-url: ${DATASOURCE_URL} + username: ${DATASOURCE_USERNAME} + password: ${DATASOURCE_PASSWORD} + +security: + jwt: + token: + secret-key: ${JWT_SECRET_KEY} + ttl-seconds: ${JWT_TOKEN_TTL_SECONDS} + +payment: + confirm-secret-key: ${TOSS_SECRET_KEY} + read-timeout: ${PAYMENT_CLIENT_READ_TIMEOUT} + connect-timeout: ${PAYMENT_CLIENT_CONNECT_TIMEOUT} + +slow-query: + logger-name: ${SLOW_QUERY_LOGGER} + log-level: ${SLOW_QUERY_LOG_LEVEL} + threshold_ms: ${SLOW_QUERY_LOGGING_THRESHOLD} + +management: + tracing: + sampling: + probability: ${TRACE_SAMPLING_PROBABILITY} \ No newline at end of file -- 2.47.2 From a49d36ed346df7936c21ee60a3904da99cb4af75 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 16:21:42 +0900 Subject: [PATCH 18/25] =?UTF-8?q?feat:=20=EB=B2=A0=ED=8F=AC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=9A=A9=20logback=20=EC=84=A4=EC=A0=95=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 --- src/main/resources/logback-deploy.xml | 95 +++++++++++++++++++++++++++ src/main/resources/logback-spring.xml | 4 ++ 2 files changed, 99 insertions(+) create mode 100644 src/main/resources/logback-deploy.xml diff --git a/src/main/resources/logback-deploy.xml b/src/main/resources/logback-deploy.xml new file mode 100644 index 00000000..662eea5e --- /dev/null +++ b/src/main/resources/logback-deploy.xml @@ -0,0 +1,95 @@ + + + + + + + + + timestamp + UTC + + + level + + + logger + + + thread + + + + + { + "message": "%maskedMessage" + } + + + + stack_trace + + 5 + 2048 + true + + + + + + + + + + logs/application-%d{yyyy-MM-dd}.%i.log.gz + 100MB + 3 + 1GB + + + + + timestamp + UTC + + + level + + + logger + + + thread + + + + + { + "message": "%maskedMessage" + } + + + + stack_trace + + 5 + 2048 + true + + + + + + + + + 512 + 0 false + + + + + + + diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index d60c1c92..a4773a2b 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,5 +1,9 @@ + + + + -- 2.47.2 From cfd35b875b73270609652c9db2b5edd4a43f0416 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 2 Aug 2025 16:32:34 +0900 Subject: [PATCH 19/25] =?UTF-8?q?feat:=20application-deploy=EC=97=90=20Tra?= =?UTF-8?q?cing=20=EC=98=B5=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-deploy.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application-deploy.yaml b/src/main/resources/application-deploy.yaml index e390837e..6fc8619b 100644 --- a/src/main/resources/application-deploy.yaml +++ b/src/main/resources/application-deploy.yaml @@ -32,4 +32,8 @@ slow-query: management: tracing: sampling: - probability: ${TRACE_SAMPLING_PROBABILITY} \ No newline at end of file + probability: ${TRACE_SAMPLING_PROBABILITY} + otlp: + tracing: + transport: ${OTLP_TRACING_PROTOCOL} + endpoint: ${OTLP_TRACING_ENDPOINT} -- 2.47.2 From 15b9ef4a08895628c77ca9bade3ed643d0c3d512 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sun, 3 Aug 2025 20:48:34 +0900 Subject: [PATCH 20/25] =?UTF-8?q?refactor:=20CORS=20origin=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=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 --- src/main/kotlin/roomescape/common/config/CorsConfig.kt | 7 ++++++- src/main/resources/application-deploy.yaml | 2 ++ src/main/resources/application-local.yaml | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/roomescape/common/config/CorsConfig.kt b/src/main/kotlin/roomescape/common/config/CorsConfig.kt index f66ed3fc..dfc8c9cd 100644 --- a/src/main/kotlin/roomescape/common/config/CorsConfig.kt +++ b/src/main/kotlin/roomescape/common/config/CorsConfig.kt @@ -1,14 +1,19 @@ package roomescape.common.config +import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Configuration import org.springframework.web.servlet.config.annotation.CorsRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration class CorsConfig : WebMvcConfigurer { + + @Value("\${cors-origin}") + private lateinit var origin: String + override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:5173") + .allowedOrigins(origin) .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") .allowedHeaders("Authorization", "Content-Type") .maxAge(3600) // 1 hour diff --git a/src/main/resources/application-deploy.yaml b/src/main/resources/application-deploy.yaml index 6fc8619b..27e92be5 100644 --- a/src/main/resources/application-deploy.yaml +++ b/src/main/resources/application-deploy.yaml @@ -37,3 +37,5 @@ management: tracing: transport: ${OTLP_TRACING_PROTOCOL} endpoint: ${OTLP_TRACING_ENDPOINT} + +cors-origin: ${CORS_ORIGIN} \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 9d0c5b24..b69b1952 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -44,4 +44,6 @@ jdbc: management: tracing: sampling: - probability: 1 \ No newline at end of file + probability: 1 + +cors-origin: "http://localhost:5173" \ No newline at end of file -- 2.47.2 From 71a106cde5ccb505b212032df4335d6561a399ad Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 4 Aug 2025 14:51:44 +0900 Subject: [PATCH 21/25] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=82=A4=EB=A7=88?= =?UTF-8?q?=20auditing=20=EB=B3=80=EC=88=98=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(->=20datetime(6))?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema/schema-mysql.sql | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/resources/schema/schema-mysql.sql b/src/main/resources/schema/schema-mysql.sql index b13d5035..e10c4072 100644 --- a/src/main/resources/schema/schema-mysql.sql +++ b/src/main/resources/schema/schema-mysql.sql @@ -5,8 +5,8 @@ create table if not exists members name varchar(255) not null, password varchar(255) not null, role varchar(20) not null, - created_at datetime null, - last_modified_at datetime null + created_at datetime(6) null, + last_modified_at datetime(6) null ); create table if not exists themes @@ -15,16 +15,16 @@ create table if not exists themes description varchar(255) not null, name varchar(255) not null, thumbnail varchar(255) not null, - created_at datetime null, - last_modified_at datetime null + created_at datetime(6) null, + last_modified_at datetime(6) null ); create table if not exists times ( - time_id bigint primary key, - start_at time(6) not null, - created_at datetime null, - last_modified_at datetime null + time_id bigint primary key, + start_at time(6) not null, + created_at datetime(6) null, + last_modified_at datetime(6) null ); create table if not exists reservations @@ -35,8 +35,8 @@ create table if not exists reservations theme_id bigint not null, time_id bigint not null, status varchar(30) not null, - created_at datetime null, - last_modified_at datetime null, + created_at datetime(6) null, + last_modified_at datetime(6) null, constraint fk_reservations__themeId foreign key (theme_id) references themes (theme_id), constraint fk_reservations__memberId foreign key (member_id) references members (member_id), constraint fk_reservations__timeId foreign key (time_id) references times (time_id) @@ -50,8 +50,8 @@ create table if not exists payments total_amount bigint not null, order_id varchar(255) not null, payment_key varchar(255) not null, - created_at datetime null, - last_modified_at datetime null, + created_at datetime(6) null, + last_modified_at datetime(6) null, constraint uk_payments__reservationId unique (reservation_id), constraint fk_payments__reservationId foreign key (reservation_id) references reservations (reservation_id) ); @@ -64,6 +64,6 @@ create table if not exists canceled_payments cancel_amount bigint not null, approved_at datetime(6) not null, canceled_at datetime(6) not null, - created_at datetime null, - last_modified_at datetime null + created_at datetime(6) null, + last_modified_at datetime(6) null ); -- 2.47.2 From b2ff1332734d071fc38c838ad4988daf69c3cd85 Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 4 Aug 2025 14:52:38 +0900 Subject: [PATCH 22/25] =?UTF-8?q?fix:=20JWT=20TTL=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt | 2 +- src/main/resources/application-local.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt b/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt index c8e611fc..0e224cb2 100644 --- a/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt +++ b/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt @@ -22,7 +22,7 @@ class JwtHandler( fun createToken(memberId: Long): String { val date = Date() - val accessTokenExpiredAt = Date(date.time + tokenTtlSeconds) + val accessTokenExpiredAt = Date(date.time + (tokenTtlSeconds * 1_000)) return Jwts.builder() .claim(MEMBER_ID_CLAIM_KEY, memberId) diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index b69b1952..37f62b36 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -23,7 +23,7 @@ security: jwt: token: secret-key: daijawligagaf@LIJ$@U)9nagnalkkgalijaddljfi - ttl-seconds: 1800000 + ttl-seconds: 1800 payment: confirm-secret-key: test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6 -- 2.47.2 From 7a79ad68d6e865a86c9503f210ba501b569726fa Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 4 Aug 2025 14:55:23 +0900 Subject: [PATCH 23/25] =?UTF-8?q?remove:=20CORS=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=EC=97=94=EB=93=9C=20API=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.env | 2 +- frontend/src/api/apiClient.ts | 2 +- frontend/vite.config.ts | 15 +++++++++++++ .../roomescape/common/config/CorsConfig.kt | 21 ------------------- src/main/resources/application-deploy.yaml | 2 -- src/main/resources/application-local.yaml | 2 -- 6 files changed, 17 insertions(+), 27 deletions(-) delete mode 100644 src/main/kotlin/roomescape/common/config/CorsConfig.kt diff --git a/frontend/.env b/frontend/.env index 2717bd56..2131aae2 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1 @@ -VITE_API_BASE_URL = "http://localhost:8080" \ No newline at end of file +VITE_API_BASE_URL = '/api' \ No newline at end of file diff --git a/frontend/src/api/apiClient.ts b/frontend/src/api/apiClient.ts index 83090c44..6e488ac3 100644 --- a/frontend/src/api/apiClient.ts +++ b/frontend/src/api/apiClient.ts @@ -1,7 +1,7 @@ import axios, { type AxiosError, type AxiosRequestConfig, type Method } from 'axios'; const apiClient = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080', + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 10000, }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1c97543f..9fe3fabb 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -8,4 +8,19 @@ export default defineConfig({ react(), tsconfigPaths(), ], + server: { + proxy: { + '/api': { + // 실제 백엔드 서버 주소로 전달 + target: 'http://localhost:8080', + + // Origin 헤더를 target의 Origin으로 변경 (CORS 에러 방지) + changeOrigin: true, + + // Ingress의 rewrite-target과 동일한 역할. + // '/api/themes' -> '/themes'로 경로를 재작성하여 백엔드에 전달 + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, + }, }) diff --git a/src/main/kotlin/roomescape/common/config/CorsConfig.kt b/src/main/kotlin/roomescape/common/config/CorsConfig.kt deleted file mode 100644 index dfc8c9cd..00000000 --- a/src/main/kotlin/roomescape/common/config/CorsConfig.kt +++ /dev/null @@ -1,21 +0,0 @@ -package roomescape.common.config - -import org.springframework.beans.factory.annotation.Value -import org.springframework.context.annotation.Configuration -import org.springframework.web.servlet.config.annotation.CorsRegistry -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer - -@Configuration -class CorsConfig : WebMvcConfigurer { - - @Value("\${cors-origin}") - private lateinit var origin: String - - override fun addCorsMappings(registry: CorsRegistry) { - registry.addMapping("/**") - .allowedOrigins(origin) - .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") - .allowedHeaders("Authorization", "Content-Type") - .maxAge(3600) // 1 hour - } -} diff --git a/src/main/resources/application-deploy.yaml b/src/main/resources/application-deploy.yaml index 27e92be5..6fc8619b 100644 --- a/src/main/resources/application-deploy.yaml +++ b/src/main/resources/application-deploy.yaml @@ -37,5 +37,3 @@ management: tracing: transport: ${OTLP_TRACING_PROTOCOL} endpoint: ${OTLP_TRACING_ENDPOINT} - -cors-origin: ${CORS_ORIGIN} \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 37f62b36..3ed9a706 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -45,5 +45,3 @@ management: tracing: sampling: probability: 1 - -cors-origin: "http://localhost:5173" \ No newline at end of file -- 2.47.2 From 1172a685fc424e378c7b6952cf132630b46a3c92 Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 4 Aug 2025 14:55:35 +0900 Subject: [PATCH 24/25] =?UTF-8?q?feat:=20forward-header=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 --- src/main/resources/application-deploy.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application-deploy.yaml b/src/main/resources/application-deploy.yaml index 6fc8619b..c57acd55 100644 --- a/src/main/resources/application-deploy.yaml +++ b/src/main/resources/application-deploy.yaml @@ -1,3 +1,6 @@ +server: + forward-headers-strategy: framework + spring: sql: init: -- 2.47.2 From 329139b87c1567753f5a6f6408315ff20ade66db Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 4 Aug 2025 14:56:03 +0900 Subject: [PATCH 25/25] =?UTF-8?q?fix:=20dockerfile=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(gradle=20->=20./gradlew)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9bbe253d..87d42727 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM gradle:8-jdk17 AS builder WORKDIR /app COPY . . -RUN gradle bootJar --no-daemon +RUN ./gradlew bootjar --no-daemon FROM amazoncorretto:17 WORKDIR /app -- 2.47.2