generated from pricelees/issue-pr-template
Compare commits
2 Commits
main
...
refactor/#
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f47cf5619 | |||
| ce75ceb0ca |
845
query.md
Normal file
845
query.md
Normal file
@ -0,0 +1,845 @@
|
|||||||
|
## Auth
|
||||||
|
|
||||||
|
**로그인**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 회원
|
||||||
|
|
||||||
|
-- 이메일로 회원 조회
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
WHERE
|
||||||
|
u.email = ?
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 연락처로 회원 조회
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
WHERE
|
||||||
|
u.phone = ?
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 회원 추가
|
||||||
|
INSERT INTO users (
|
||||||
|
created_at, created_by, email, name, password, phone, region_code,
|
||||||
|
status, updated_at, updated_by, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 회원 상태 이력 추가
|
||||||
|
INSERT INTO user_status_history (
|
||||||
|
created_at, created_by, reason, status, updated_at, updated_by,
|
||||||
|
user_id, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Payment
|
||||||
|
|
||||||
|
**결제 승인 & 저장**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 결제 정보 추가
|
||||||
|
INSERT INTO payment ( approved_at, method, order_id, payment_key, requested_at, reservation_id, status, total_amount, type, id
|
||||||
|
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 결제 상세 정보 추가
|
||||||
|
INSERT INTO payment_detail ( payment_id, supplied_amount, vat, id
|
||||||
|
) VALUES ( ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
-- 카드 결제 상세 정보 추가
|
||||||
|
INSERT INTO payment_card_detail ( amount, approval_number, card_number, card_type, easypay_discount_amount, easypay_provider_code, installment_plan_months, is_interest_free, issuer_code, owner_type, id
|
||||||
|
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**결제 취소**
|
||||||
|
|
||||||
|
SQL
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 예약 ID로 결제 정보 조회
|
||||||
|
SELECT
|
||||||
|
p.id,
|
||||||
|
p.approved_at,
|
||||||
|
p.method,
|
||||||
|
p.order_id,
|
||||||
|
p.payment_key,
|
||||||
|
p.requested_at,
|
||||||
|
p.reservation_id,
|
||||||
|
p.status,
|
||||||
|
p.total_amount,
|
||||||
|
p.type
|
||||||
|
FROM
|
||||||
|
payment p
|
||||||
|
WHERE
|
||||||
|
p.reservation_id = ?;
|
||||||
|
|
||||||
|
-- 추가
|
||||||
|
-- 취소된 결제 정보 추가
|
||||||
|
INSERT INTO canceled_payment (
|
||||||
|
cancel_amount, cancel_reason, canceled_at, canceled_by,
|
||||||
|
card_discount_amount, easypay_discount_amount, payment_id,
|
||||||
|
requested_at, transfer_discount_amount, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Region
|
||||||
|
|
||||||
|
**모든 시/도 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT
|
||||||
|
r.sido_code,
|
||||||
|
r.sido_name
|
||||||
|
FROM
|
||||||
|
region r
|
||||||
|
ORDER BY
|
||||||
|
r.sido_name;
|
||||||
|
```
|
||||||
|
|
||||||
|
**시/군/구 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
r.sigungu_code,
|
||||||
|
r.sigungu_name
|
||||||
|
FROM
|
||||||
|
region r
|
||||||
|
WHERE
|
||||||
|
r.sido_code = ?
|
||||||
|
GROUP BY
|
||||||
|
r.sigungu_code, r.sigungu_name
|
||||||
|
ORDER BY
|
||||||
|
r.sigungu_name;
|
||||||
|
```
|
||||||
|
|
||||||
|
**지역 코드 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
r.code
|
||||||
|
FROM
|
||||||
|
region r
|
||||||
|
WHERE
|
||||||
|
r.sido_code = ? AND r.sigungu_code = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reservation
|
||||||
|
|
||||||
|
**Pending 예약 생성**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- schedule 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- theme 조회
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.id = ?;
|
||||||
|
|
||||||
|
-- 예약 추가
|
||||||
|
INSERT INTO reservation (
|
||||||
|
created_at, created_by, participant_count, requirement,
|
||||||
|
reserver_contact, reserver_name, schedule_id, status,
|
||||||
|
updated_at, updated_by, user_id, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**확정**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 예약 조회
|
||||||
|
SELECT
|
||||||
|
r.id, r.created_at, r.created_by, r.participant_count, r.requirement,
|
||||||
|
r.reserver_contact, r.reserver_name, r.schedule_id, r.status,
|
||||||
|
r.updated_at, r.updated_by, r.user_id
|
||||||
|
FROM
|
||||||
|
reservation r
|
||||||
|
WHERE
|
||||||
|
r.id = ?;
|
||||||
|
|
||||||
|
-- 일정 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- 예약 확정
|
||||||
|
UPDATE
|
||||||
|
reservation
|
||||||
|
SET
|
||||||
|
participant_count = ?, requirement = ?, reserver_contact = ?,
|
||||||
|
reserver_name = ?, schedule_id = ?, status = ?,
|
||||||
|
updated_at = ?, updated_by = ?, user_id = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
|
||||||
|
-- Schedule 확정
|
||||||
|
UPDATE
|
||||||
|
schedule
|
||||||
|
SET
|
||||||
|
date = ?, status = ?, store_id = ?, theme_id = ?, time = ?,
|
||||||
|
updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**취소**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 예약 조회
|
||||||
|
SELECT
|
||||||
|
r.id, r.created_at, r.created_by, r.participant_count, r.requirement,
|
||||||
|
r.reserver_contact, r.reserver_name, r.schedule_id, r.status,
|
||||||
|
r.updated_at, r.updated_by, r.user_id
|
||||||
|
FROM
|
||||||
|
reservation r
|
||||||
|
WHERE
|
||||||
|
r.id = ?;
|
||||||
|
|
||||||
|
-- 일정 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- 취소 예약 추가
|
||||||
|
INSERT INTO canceled_reservation (
|
||||||
|
cancel_reason, canceled_at, canceled_by,
|
||||||
|
reservation_id, status, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 예약 취소
|
||||||
|
UPDATE
|
||||||
|
reservation
|
||||||
|
SET
|
||||||
|
participant_count = ?, requirement = ?, reserver_contact = ?,
|
||||||
|
reserver_name = ?, schedule_id = ?, status = ?,
|
||||||
|
updated_at = ?, updated_by = ?, user_id = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
|
||||||
|
-- 일정 활성화
|
||||||
|
UPDATE
|
||||||
|
schedule
|
||||||
|
SET
|
||||||
|
date = ?, status = ?, store_id = ?, theme_id = ?, time = ?,
|
||||||
|
updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**회원 예약 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 예약 조회
|
||||||
|
SELECT
|
||||||
|
r.id, r.created_at, r.created_by, r.participant_count, r.requirement,
|
||||||
|
r.reserver_contact, r.reserver_name, r.schedule_id, r.status,
|
||||||
|
r.updated_at, r.updated_by, r.user_id
|
||||||
|
FROM
|
||||||
|
reservation r
|
||||||
|
WHERE
|
||||||
|
r.user_id = ? AND r.status IN (?, ?);
|
||||||
|
|
||||||
|
-- 일정 조회 -> 각 예약별 1개씩(N개)
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
st.id AS store_id,
|
||||||
|
st.name AS store_name,
|
||||||
|
s.date,
|
||||||
|
s.time,
|
||||||
|
t.id AS theme_id,
|
||||||
|
t.name AS theme_name,
|
||||||
|
t.difficulty,
|
||||||
|
t.available_minutes,
|
||||||
|
s.status
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
JOIN theme t ON t.id = s.theme_id
|
||||||
|
JOIN store st ON st.id = s.store_id
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**예약 상세 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 예약 조회
|
||||||
|
SELECT
|
||||||
|
r.id, r.created_at, r.created_by, r.participant_count, r.requirement,
|
||||||
|
r.reserver_contact, r.reserver_name, r.schedule_id, r.status,
|
||||||
|
r.updated_at, r.updated_by, r.user_id
|
||||||
|
FROM
|
||||||
|
reservation r
|
||||||
|
WHERE
|
||||||
|
r.id = ?;
|
||||||
|
|
||||||
|
-- 회원 연락처 정보 조회
|
||||||
|
SELECT
|
||||||
|
u.id, u.created_at, u.created_by, u.email, u.name, u.password,
|
||||||
|
u.phone, u.region_code, u.status, u.updated_at, u.updated_by
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
WHERE
|
||||||
|
u.id = ?;
|
||||||
|
|
||||||
|
-- 결제 정보 조회
|
||||||
|
SELECT
|
||||||
|
p.id, p.approved_at, p.method, p.order_id, p.payment_key,
|
||||||
|
p.requested_at, p.reservation_id, p.status, p.total_amount, p.type
|
||||||
|
FROM
|
||||||
|
payment p
|
||||||
|
WHERE
|
||||||
|
p.reservation_id = ?;
|
||||||
|
|
||||||
|
-- 결제 상세 정보 조회
|
||||||
|
SELECT
|
||||||
|
pd.id,
|
||||||
|
CASE
|
||||||
|
WHEN pbt.id IS NOT NULL THEN 1 -- bank_transfer
|
||||||
|
WHEN pcd.id IS NOT NULL THEN 2 -- card
|
||||||
|
WHEN pep.id IS NOT NULL THEN 3 -- easypay
|
||||||
|
WHEN pd.id IS NOT NULL THEN 0 -- etc
|
||||||
|
END AS payment_type,
|
||||||
|
pd.payment_id, pd.supplied_amount, pd.vat,
|
||||||
|
pbt.bank_code, pbt.settlement_status,
|
||||||
|
pcd.amount, pcd.approval_number, pcd.card_number, pcd.card_type,
|
||||||
|
pcd.easypay_discount_amount, pcd.easypay_provider_code,
|
||||||
|
pcd.installment_plan_months, pcd.is_interest_free, pcd.issuer_code,
|
||||||
|
pcd.owner_type,
|
||||||
|
pep.amount AS easypay_amount,
|
||||||
|
pep.discount_amount AS easypay_discount_amount,
|
||||||
|
pep.easypay_provider_code AS easypay_provider
|
||||||
|
FROM
|
||||||
|
payment_detail pd
|
||||||
|
LEFT JOIN payment_bank_transfer_detail pbt ON pd.id = pbt.id
|
||||||
|
LEFT JOIN payment_card_detail pcd ON pd.id = pcd.id
|
||||||
|
LEFT JOIN payment_easypay_prepaid_detail pep ON pd.id = pep.id
|
||||||
|
WHERE
|
||||||
|
pd.payment_id = ?;
|
||||||
|
|
||||||
|
-- 취소 결제 정보 조회
|
||||||
|
SELECT
|
||||||
|
cp.id, cp.cancel_amount, cp.cancel_reason, cp.canceled_at,
|
||||||
|
cp.canceled_by, cp.card_discount_amount, cp.easypay_discount_amount,
|
||||||
|
cp.payment_id, cp.requested_at, cp.transfer_discount_amount
|
||||||
|
FROM
|
||||||
|
canceled_payment cp
|
||||||
|
WHERE
|
||||||
|
cp.payment_id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schedule
|
||||||
|
|
||||||
|
**날짜, 시간, 테마로 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
st.id AS store_id,
|
||||||
|
st.name AS store_name,
|
||||||
|
s.date,
|
||||||
|
s.time,
|
||||||
|
t.id AS theme_id,
|
||||||
|
t.name AS theme_name,
|
||||||
|
t.difficulty,
|
||||||
|
t.available_minutes,
|
||||||
|
s.status
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
JOIN theme t ON t.id = s.theme_id AND (? IS NULL OR t.id = ?)
|
||||||
|
JOIN store st ON st.id = s.store_id AND st.id = ?
|
||||||
|
WHERE
|
||||||
|
s.date = ?
|
||||||
|
```
|
||||||
|
|
||||||
|
**감사 정보 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 일정 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- 작업자 조회(createdBy, updatedBy)
|
||||||
|
SELECT
|
||||||
|
a.id, a.account, a.created_at, a.created_by, a.name, a.password,
|
||||||
|
a.permission_level, a.phone, a.store_id, a.type, a.updated_at,
|
||||||
|
a.updated_by
|
||||||
|
FROM
|
||||||
|
admin a
|
||||||
|
WHERE
|
||||||
|
a.id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**일정 생성**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 날짜, 시간, 테마가 같은 일정 존재 여부 확인
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM schedule s
|
||||||
|
WHERE
|
||||||
|
s.store_id = ?
|
||||||
|
AND s.date = ?
|
||||||
|
AND s.theme_id = ?
|
||||||
|
AND s.time = ?
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 시간이 겹치는 같은 날의 일정이 있는지 확인
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
st.id AS store_id,
|
||||||
|
st.name AS store_name,
|
||||||
|
s.date,
|
||||||
|
s.time,
|
||||||
|
t.id AS theme_id,
|
||||||
|
t.name AS theme_name,
|
||||||
|
t.difficulty,
|
||||||
|
t.available_minutes,
|
||||||
|
s.status
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
JOIN theme t ON t.id = s.theme_id AND (? IS NULL OR s.theme_id = ?)
|
||||||
|
JOIN store st ON st.id = s.store_id AND st.id = ?
|
||||||
|
WHERE
|
||||||
|
s.date = ?
|
||||||
|
|
||||||
|
-- 일정 추가
|
||||||
|
INSERT INTO schedule (
|
||||||
|
created_at, created_by, date, status, store_id,
|
||||||
|
theme_id, time, updated_at, updated_by, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**일정 수정**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- 수정
|
||||||
|
UPDATE
|
||||||
|
schedule
|
||||||
|
SET
|
||||||
|
date = ?, status = ?, store_id = ?, theme_id = ?, time = ?,
|
||||||
|
updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**일정 삭제**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- 삭제
|
||||||
|
DELETE FROM schedule
|
||||||
|
WHERE id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**상태 → HOLD 변경**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.created_at, s.created_by, s.date, s.status, s.store_id,
|
||||||
|
s.theme_id, s.time, s.updated_at, s.updated_by
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
WHERE
|
||||||
|
s.id = ?;
|
||||||
|
|
||||||
|
-- 수정
|
||||||
|
UPDATE
|
||||||
|
schedule
|
||||||
|
SET
|
||||||
|
date = ?, status = ?, store_id = ?, theme_id = ?, time = ?,
|
||||||
|
updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Store
|
||||||
|
|
||||||
|
**매장 상세 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.address, s.business_reg_num, s.contact, s.created_at,
|
||||||
|
s.created_by, s.name, s.region_code, s.status, s.updated_at,
|
||||||
|
s.updated_by
|
||||||
|
FROM
|
||||||
|
store s
|
||||||
|
WHERE
|
||||||
|
s.id = ? AND s.status = 'ACTIVE';
|
||||||
|
|
||||||
|
-- 지역 정보 조회
|
||||||
|
SELECT
|
||||||
|
r.code, r.sido_code, r.sido_name, r.sigungu_code, r.sigungu_name
|
||||||
|
FROM
|
||||||
|
region r
|
||||||
|
WHERE
|
||||||
|
r.code = ?;
|
||||||
|
|
||||||
|
-- 감사 정보 조회(createdBy, updatedBy)
|
||||||
|
SELECT
|
||||||
|
a.id, a.account, a.created_at, a.created_by, a.name, a.password,
|
||||||
|
a.permission_level, a.phone, a.store_id, a.type, a.updated_at,
|
||||||
|
a.updated_by
|
||||||
|
FROM
|
||||||
|
admin a
|
||||||
|
WHERE
|
||||||
|
a.id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**매장 등록**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 이름 중복 확인
|
||||||
|
SELECT s.id FROM store s WHERE s.name = ? LIMIT 1;
|
||||||
|
|
||||||
|
-- 연락처 중복 확인
|
||||||
|
SELECT s.id FROM store s WHERE s.contact = ? LIMIT 1;
|
||||||
|
|
||||||
|
-- 주소 중복 확인
|
||||||
|
SELECT s.id FROM store s WHERE s.address = ? LIMIT 1;
|
||||||
|
|
||||||
|
-- 사업자번호 중복 확인
|
||||||
|
SELECT s.id FROM store s WHERE s.business_reg_num = ? LIMIT 1;
|
||||||
|
|
||||||
|
-- 추가
|
||||||
|
INSERT INTO store (
|
||||||
|
address, business_reg_num, contact, created_at, created_by,
|
||||||
|
name, region_code, status, updated_at, updated_by, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**매장 수정**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.address, s.business_reg_num, s.contact, s.created_at,
|
||||||
|
s.created_by, s.name, s.region_code, s.status, s.updated_at,
|
||||||
|
s.updated_by
|
||||||
|
FROM
|
||||||
|
store s
|
||||||
|
WHERE
|
||||||
|
s.id = ? AND s.status = 'ACTIVE';
|
||||||
|
|
||||||
|
-- 수정
|
||||||
|
UPDATE
|
||||||
|
store
|
||||||
|
SET
|
||||||
|
address = ?, business_reg_num = ?, contact = ?, name = ?,
|
||||||
|
region_code = ?, status = ?, updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**비활성화(status = DISABLE)**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
s.id, s.address, s.business_reg_num, s.contact, s.created_at,
|
||||||
|
s.created_by, s.name, s.region_code, s.status, s.updated_at,
|
||||||
|
s.updated_by
|
||||||
|
FROM
|
||||||
|
store s
|
||||||
|
WHERE
|
||||||
|
s.id = ? AND s.status = 'ACTIVE';
|
||||||
|
|
||||||
|
-- 수정
|
||||||
|
UPDATE
|
||||||
|
store
|
||||||
|
SET
|
||||||
|
address = ?, business_reg_num = ?, contact = ?, name = ?,
|
||||||
|
region_code = ?, status = ?, updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**모든 매장 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
s.id, s.address, s.business_reg_num, s.contact, s.created_at,
|
||||||
|
s.created_by, s.name, s.region_code, s.status, s.updated_at,
|
||||||
|
s.updated_by
|
||||||
|
FROM
|
||||||
|
store s
|
||||||
|
WHERE
|
||||||
|
s.status = 'ACTIVE'
|
||||||
|
AND (? IS NULL OR s.region_code LIKE ?);
|
||||||
|
```
|
||||||
|
|
||||||
|
**개별 매장 상세 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
s.id, s.address, s.business_reg_num, s.contact, s.created_at,
|
||||||
|
s.created_by, s.name, s.region_code, s.status, s.updated_at,
|
||||||
|
s.updated_by
|
||||||
|
FROM
|
||||||
|
store s
|
||||||
|
WHERE
|
||||||
|
s.id = ? AND s.status = 'ACTIVE';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme
|
||||||
|
|
||||||
|
**생성**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 이름으로 조회
|
||||||
|
SELECT
|
||||||
|
t.id
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.name = ?
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 추가
|
||||||
|
INSERT INTO theme (
|
||||||
|
available_minutes, created_at, created_by, description, difficulty,
|
||||||
|
expected_minutes_from, expected_minutes_to, is_active, max_participants,
|
||||||
|
min_participants, name, price, thumbnail_url, updated_at, updated_by, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Active인 모든 테마 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.is_active = TRUE;
|
||||||
|
```
|
||||||
|
|
||||||
|
**테마 목록 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t;
|
||||||
|
```
|
||||||
|
|
||||||
|
**감사 정보 포함 개별 테마 상세 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**개별 테마 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**삭제**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.id = ?;
|
||||||
|
|
||||||
|
-- 삭제
|
||||||
|
DELETE FROM theme WHERE id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**수정**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 조회
|
||||||
|
SELECT
|
||||||
|
t.id, t.available_minutes, t.created_at, t.created_by, t.description,
|
||||||
|
t.difficulty, t.expected_minutes_from, t.expected_minutes_to,
|
||||||
|
t.is_active, t.max_participants, t.min_participants, t.name,
|
||||||
|
t.price, t.thumbnail_url, t.updated_at, t.updated_by
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
WHERE
|
||||||
|
t.id = ?;
|
||||||
|
|
||||||
|
-- 수정
|
||||||
|
UPDATE
|
||||||
|
theme
|
||||||
|
SET
|
||||||
|
available_minutes = ?, description = ?, difficulty = ?,
|
||||||
|
expected_minutes_from = ?, expected_minutes_to = ?, is_active = ?,
|
||||||
|
max_participants = ?, min_participants = ?, name = ?, price = ?,
|
||||||
|
thumbnail_url = ?, updated_at = ?, updated_by = ?
|
||||||
|
WHERE
|
||||||
|
id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**인기 테마 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
t.id, t.name, t.description, t.difficulty, t.thumbnail_url, t.price,
|
||||||
|
t.min_participants, t.max_participants,
|
||||||
|
t.available_minutes, t.expected_minutes_from, t.expected_minutes_to
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
s.theme_id, count(*) as reservation_count
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
JOIN
|
||||||
|
reservation r ON s.id = r.schedule_id AND r.status = 'CONFIRMED'
|
||||||
|
WHERE
|
||||||
|
s.status = 'RESERVED'
|
||||||
|
AND (s.date BETWEEN :startFrom AND :endAt)
|
||||||
|
GROUP BY
|
||||||
|
s.theme_id
|
||||||
|
ORDER BY
|
||||||
|
reservation_count desc
|
||||||
|
LIMIT :count
|
||||||
|
) ranked_themes ON t.id = ranked_themes.theme_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### User
|
||||||
|
|
||||||
|
**회원가입**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 이메일 중복 확인
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
WHERE
|
||||||
|
u.email = ?
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 연락처 중복 확인
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
WHERE
|
||||||
|
u.phone = ?
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 추가
|
||||||
|
INSERT INTO users (
|
||||||
|
created_at, created_by, email, name, password, phone, region_code,
|
||||||
|
status, updated_at, updated_by, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 상태 변경 이력 추가
|
||||||
|
INSERT INTO user_status_history (
|
||||||
|
created_at, created_by, reason, status, updated_at, updated_by,
|
||||||
|
user_id, id
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**연락처 정보 조회**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
u.id, u.created_at, u.created_by, u.email, u.name, u.password,
|
||||||
|
u.phone, u.region_code, u.status, u.updated_at, u.updated_by
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
WHERE
|
||||||
|
u.id = ?;
|
||||||
|
```
|
||||||
@ -8,10 +8,6 @@ dependencies {
|
|||||||
// API docs
|
// API docs
|
||||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
|
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
|
||||||
|
|
||||||
// Cache
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-cache")
|
|
||||||
implementation("com.github.ben-manes.caffeine:caffeine")
|
|
||||||
|
|
||||||
// DB
|
// DB
|
||||||
runtimeOnly("com.h2database:h2")
|
runtimeOnly("com.h2database:h2")
|
||||||
runtimeOnly("com.mysql:mysql-connector-j")
|
runtimeOnly("com.mysql:mysql-connector-j")
|
||||||
|
|||||||
@ -3,14 +3,8 @@ package com.sangdol.roomescape
|
|||||||
import org.springframework.boot.Banner
|
import org.springframework.boot.Banner
|
||||||
import org.springframework.boot.SpringApplication
|
import org.springframework.boot.SpringApplication
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.cache.annotation.EnableCaching
|
|
||||||
import org.springframework.scheduling.annotation.EnableAsync
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@EnableAsync
|
|
||||||
@EnableCaching
|
|
||||||
@EnableScheduling
|
|
||||||
@SpringBootApplication(
|
@SpringBootApplication(
|
||||||
scanBasePackages = ["com.sangdol.roomescape", "com.sangdol.common"]
|
scanBasePackages = ["com.sangdol.roomescape", "com.sangdol.common"]
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class AdminService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findCredentialsByAccount(account: String): AdminLoginCredentials {
|
fun findCredentialsByAccount(account: String): AdminLoginCredentials {
|
||||||
log.debug { "[findCredentialsByAccount] 관리자 조회 시작: account=${account}" }
|
log.info { "[findCredentialsByAccount] 관리자 조회 시작: account=${account}" }
|
||||||
|
|
||||||
return adminRepository.findByAccount(account)
|
return adminRepository.findByAccount(account)
|
||||||
?.let {
|
?.let {
|
||||||
@ -28,14 +28,14 @@ class AdminService(
|
|||||||
it.toCredentials()
|
it.toCredentials()
|
||||||
}
|
}
|
||||||
?: run {
|
?: run {
|
||||||
log.debug { "[findCredentialsByAccount] 관리자 조회 실패: account=${account}" }
|
log.info { "[findCredentialsByAccount] 관리자 조회 실패: account=${account}" }
|
||||||
throw AdminException(AdminErrorCode.ADMIN_NOT_FOUND)
|
throw AdminException(AdminErrorCode.ADMIN_NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findOperatorOrUnknown(id: Long): Auditor {
|
fun findOperatorOrUnknown(id: Long): Auditor {
|
||||||
log.debug { "[findOperatorById] 작업자 정보 조회 시작: id=${id}" }
|
log.info { "[findOperatorById] 작업자 정보 조회 시작: id=${id}" }
|
||||||
|
|
||||||
return adminRepository.findByIdOrNull(id)?.let { admin ->
|
return adminRepository.findByIdOrNull(id)?.let { admin ->
|
||||||
Auditor(admin.id, admin.name).also {
|
Auditor(admin.id, admin.name).also {
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class AuthService(
|
|||||||
request: LoginRequest,
|
request: LoginRequest,
|
||||||
context: LoginContext
|
context: LoginContext
|
||||||
): LoginSuccessResponse {
|
): LoginSuccessResponse {
|
||||||
log.debug { "[login] 로그인 시작: account=${request.account}, type=${request.principalType}, context=${context}" }
|
log.info { "[login] 로그인 시작: account=${request.account}, type=${request.principalType}, context=${context}" }
|
||||||
val (credentials, extraClaims) = getCredentials(request)
|
val (credentials, extraClaims) = getCredentials(request)
|
||||||
|
|
||||||
val event = LoginHistoryEvent(
|
val event = LoginHistoryEvent(
|
||||||
@ -56,7 +56,10 @@ class AuthService(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
eventPublisher.publishEvent(event.onFailure())
|
eventPublisher.publishEvent(event.onFailure())
|
||||||
when (e) {
|
when (e) {
|
||||||
is AuthException -> { throw e }
|
is AuthException -> {
|
||||||
|
log.info { "[login] 로그인 실패: account = ${request.account}" }
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
log.warn { "[login] 로그인 실패: message=${e.message} account = ${request.account}" }
|
log.warn { "[login] 로그인 실패: message=${e.message} account = ${request.account}" }
|
||||||
@ -71,7 +74,7 @@ class AuthService(
|
|||||||
credentials: LoginCredentials
|
credentials: LoginCredentials
|
||||||
) {
|
) {
|
||||||
if (credentials.password != request.password) {
|
if (credentials.password != request.password) {
|
||||||
log.debug { "[login] 비밀번호 불일치로 인한 로그인 실패: account = ${request.account}" }
|
log.info { "[login] 비밀번호 불일치로 인한 로그인 실패: account = ${request.account}" }
|
||||||
throw AuthException(AuthErrorCode.LOGIN_FAILED)
|
throw AuthException(AuthErrorCode.LOGIN_FAILED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import jakarta.annotation.PreDestroy
|
|||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.event.EventListener
|
import org.springframework.context.event.EventListener
|
||||||
import org.springframework.scheduling.annotation.Async
|
import org.springframework.scheduling.annotation.Async
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling
|
||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
@ -19,6 +21,8 @@ import java.util.concurrent.TimeUnit
|
|||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@EnableAsync
|
||||||
|
@EnableScheduling
|
||||||
class LoginHistoryEventListener(
|
class LoginHistoryEventListener(
|
||||||
private val idGenerator: IDGenerator,
|
private val idGenerator: IDGenerator,
|
||||||
private val loginHistoryRepository: LoginHistoryRepository,
|
private val loginHistoryRepository: LoginHistoryRepository,
|
||||||
@ -31,7 +35,7 @@ class LoginHistoryEventListener(
|
|||||||
@Async
|
@Async
|
||||||
@EventListener(classes = [LoginHistoryEvent::class])
|
@EventListener(classes = [LoginHistoryEvent::class])
|
||||||
fun onLoginCompleted(event: LoginHistoryEvent) {
|
fun onLoginCompleted(event: LoginHistoryEvent) {
|
||||||
log.debug { "[onLoginCompleted] 로그인 이력 저장 이벤트 수신: id=${event.id}, type=${event.type}" }
|
log.info { "[onLoginCompleted] 로그인 이력 저장 이벤트 수신: id=${event.id}, type=${event.type}" }
|
||||||
|
|
||||||
queue.add(event.toEntity(idGenerator.create())).also {
|
queue.add(event.toEntity(idGenerator.create())).also {
|
||||||
log.info { "[onLoginCompleted] 로그인 이력 저장 이벤트 큐 저장 완료: id=${event.id}, type=${event.type}" }
|
log.info { "[onLoginCompleted] 로그인 이력 저장 이벤트 큐 저장 완료: id=${event.id}, type=${event.type}" }
|
||||||
@ -44,10 +48,10 @@ class LoginHistoryEventListener(
|
|||||||
|
|
||||||
@Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS)
|
@Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS)
|
||||||
fun flushScheduled() {
|
fun flushScheduled() {
|
||||||
log.debug { "[flushScheduled] 큐에 저장된 로그인 이력 저장 시작: size=${queue.size}" }
|
log.info { "[flushScheduled] 큐에 저장된 로그인 이력 저장 시작: size=${queue.size}" }
|
||||||
|
|
||||||
if (queue.isEmpty()) {
|
if (queue.isEmpty()) {
|
||||||
log.debug { "[flushScheduled] 큐에 있는 로그인 이력이 없음." }
|
log.info { "[flushScheduled] 큐에 있는 로그인 이력이 없음." }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
@ -56,7 +60,7 @@ class LoginHistoryEventListener(
|
|||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
fun flushAll() {
|
fun flushAll() {
|
||||||
log.debug { "[flushAll] 애플리케이션 종료. 큐에 있는 모든 이력 저장 시작: size=${queue.size}" }
|
log.info { "[flushAll] 애플리케이션 종료. 큐에 있는 모든 이력 저장 시작: size=${queue.size}" }
|
||||||
while (!queue.isEmpty()) {
|
while (!queue.isEmpty()) {
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
@ -64,10 +68,10 @@ class LoginHistoryEventListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun flush() {
|
private fun flush() {
|
||||||
log.debug { "[flush] 큐에 저장된 로그인 이력 저장 시작: size=${queue.size}" }
|
log.info { "[flush] 큐에 저장된 로그인 이력 저장 시작: size=${queue.size}" }
|
||||||
|
|
||||||
if (queue.isEmpty()) {
|
if (queue.isEmpty()) {
|
||||||
log.debug { "[flush] 큐에 있는 로그인 이력이 없음." }
|
log.info { "[flush] 큐에 있는 로그인 이력이 없음." }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class JwtUtils(
|
|||||||
val claims = extractAllClaims(token)
|
val claims = extractAllClaims(token)
|
||||||
|
|
||||||
return claims.subject ?: run {
|
return claims.subject ?: run {
|
||||||
log.debug { "[JwtUtils.extractSubject] subject를 찾을 수 없음.: token = ${token}" }
|
log.info { "[JwtUtils.extractSubject] subject를 찾을 수 없음.: token = ${token}" }
|
||||||
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class OrderService(
|
|||||||
fun confirm(reservationId: Long, paymentConfirmRequest: PaymentConfirmRequest) {
|
fun confirm(reservationId: Long, paymentConfirmRequest: PaymentConfirmRequest) {
|
||||||
val paymentKey = paymentConfirmRequest.paymentKey
|
val paymentKey = paymentConfirmRequest.paymentKey
|
||||||
|
|
||||||
log.debug { "[confirm] 결제 및 예약 확정 시작: reservationId=${reservationId}, paymentKey=${paymentKey}" }
|
log.info { "[confirm] 결제 및 예약 확정 시작: reservationId=${reservationId}, paymentKey=${paymentKey}" }
|
||||||
try {
|
try {
|
||||||
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
||||||
validateCanConfirm(reservationId)
|
validateCanConfirm(reservationId)
|
||||||
@ -42,7 +42,7 @@ class OrderService(
|
|||||||
paymentService.requestConfirm(reservationId, paymentConfirmRequest)
|
paymentService.requestConfirm(reservationId, paymentConfirmRequest)
|
||||||
eventPublisher.publishEvent(ReservationConfirmEvent(reservationId))
|
eventPublisher.publishEvent(ReservationConfirmEvent(reservationId))
|
||||||
|
|
||||||
log.info { "[confirm] 결제 처리 및 예약 확정 이벤트 발행 완료: reservationId=${reservationId}, paymentKey=${paymentKey}" }
|
log.info { "[confirm] 결제 처리 및 예약 확정 이벤트 발행 완료" }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val errorCode: ErrorCode = if (e is RoomescapeException) {
|
val errorCode: ErrorCode = if (e is RoomescapeException) {
|
||||||
e.errorCode
|
e.errorCode
|
||||||
@ -55,7 +55,7 @@ class OrderService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun validateCanConfirm(reservationId: Long) {
|
private fun validateCanConfirm(reservationId: Long) {
|
||||||
log.debug { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 시작: reservationId=${reservationId}" }
|
log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 시작: reservationId=${reservationId}" }
|
||||||
val reservation: ReservationStateResponse = reservationService.findStatusWithLock(reservationId)
|
val reservation: ReservationStateResponse = reservationService.findStatusWithLock(reservationId)
|
||||||
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(reservation.scheduleId)
|
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(reservation.scheduleId)
|
||||||
|
|
||||||
|
|||||||
@ -27,12 +27,15 @@ class OrderValidator {
|
|||||||
private fun validateReservationStatus(reservation: ReservationStateResponse) {
|
private fun validateReservationStatus(reservation: ReservationStateResponse) {
|
||||||
when (reservation.status) {
|
when (reservation.status) {
|
||||||
ReservationStatus.CONFIRMED -> {
|
ReservationStatus.CONFIRMED -> {
|
||||||
|
log.info { "[validateCanConfirm] 이미 확정된 예약: id=${reservation.id}" }
|
||||||
throw OrderException(OrderErrorCode.ORDER_ALREADY_CONFIRMED)
|
throw OrderException(OrderErrorCode.ORDER_ALREADY_CONFIRMED)
|
||||||
}
|
}
|
||||||
ReservationStatus.EXPIRED -> {
|
ReservationStatus.EXPIRED -> {
|
||||||
|
log.info { "[validateCanConfirm] 만료된 예약: id=${reservation.id}" }
|
||||||
throw OrderException(OrderErrorCode.EXPIRED_RESERVATION)
|
throw OrderException(OrderErrorCode.EXPIRED_RESERVATION)
|
||||||
}
|
}
|
||||||
ReservationStatus.CANCELED -> {
|
ReservationStatus.CANCELED -> {
|
||||||
|
log.info { "[validateCanConfirm] 취소된 예약: id=${reservation.id}" }
|
||||||
throw OrderException(OrderErrorCode.CANCELED_RESERVATION)
|
throw OrderException(OrderErrorCode.CANCELED_RESERVATION)
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -41,14 +44,14 @@ class OrderValidator {
|
|||||||
|
|
||||||
private fun validateScheduleStatus(schedule: ScheduleStateResponse) {
|
private fun validateScheduleStatus(schedule: ScheduleStateResponse) {
|
||||||
if (schedule.status != ScheduleStatus.HOLD) {
|
if (schedule.status != ScheduleStatus.HOLD) {
|
||||||
log.debug { "[validateScheduleStatus] 일정 상태 오류: status=${schedule.status}" }
|
log.info { "[validateScheduleStatus] 일정 상태 오류: status=${schedule.status}" }
|
||||||
throw OrderException(OrderErrorCode.EXPIRED_RESERVATION)
|
throw OrderException(OrderErrorCode.EXPIRED_RESERVATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
val scheduleDateTime = LocalDateTime.of(schedule.date, schedule.startFrom)
|
val scheduleDateTime = LocalDateTime.of(schedule.date, schedule.startFrom)
|
||||||
val nowDateTime = KoreaDateTime.now()
|
val nowDateTime = KoreaDateTime.now()
|
||||||
if (scheduleDateTime.isBefore(nowDateTime)) {
|
if (scheduleDateTime.isBefore(nowDateTime)) {
|
||||||
log.debug { "[validateScheduleStatus] 과거 시간인 일정으로 인한 실패: scheduleDateTime=${scheduleDateTime}(KST), now=${nowDateTime}(KST)" }
|
log.info { "[validateScheduleStatus] 과거 시간인 일정으로 인한 실패: scheduleDateTime=${scheduleDateTime}(KST), now=${nowDateTime}(KST)" }
|
||||||
throw OrderException(OrderErrorCode.PAST_SCHEDULE)
|
throw OrderException(OrderErrorCode.PAST_SCHEDULE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ class PaymentService(
|
|||||||
private val eventPublisher: ApplicationEventPublisher
|
private val eventPublisher: ApplicationEventPublisher
|
||||||
) {
|
) {
|
||||||
fun requestConfirm(reservationId: Long, request: PaymentConfirmRequest): PaymentGatewayResponse {
|
fun requestConfirm(reservationId: Long, request: PaymentConfirmRequest): PaymentGatewayResponse {
|
||||||
log.debug { "[requestConfirm] 결제 요청 시작: paymentKey=${request.paymentKey}" }
|
log.info { "[requestConfirm] 결제 요청 시작: paymentKey=${request.paymentKey}" }
|
||||||
try {
|
try {
|
||||||
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount).also {
|
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount).also {
|
||||||
eventPublisher.publishEvent(it.toEvent(reservationId))
|
eventPublisher.publishEvent(it.toEvent(reservationId))
|
||||||
@ -90,7 +90,7 @@ class PaymentService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findDetailByReservationId(reservationId: Long): PaymentResponse? {
|
fun findDetailByReservationId(reservationId: Long): PaymentResponse? {
|
||||||
log.debug { "[findDetailByReservationId] 예약 결제 정보 조회 시작: reservationId=$reservationId" }
|
log.info { "[findDetailByReservationId] 예약 결제 정보 조회 시작: reservationId=$reservationId" }
|
||||||
|
|
||||||
val payment: PaymentEntity? = findByReservationIdOrNull(reservationId)
|
val payment: PaymentEntity? = findByReservationIdOrNull(reservationId)
|
||||||
val paymentDetail: PaymentDetailEntity? = payment?.let { findDetailByPaymentIdOrNull(it.id) }
|
val paymentDetail: PaymentDetailEntity? = payment?.let { findDetailByPaymentIdOrNull(it.id) }
|
||||||
@ -99,13 +99,11 @@ class PaymentService(
|
|||||||
return payment?.toResponse(
|
return payment?.toResponse(
|
||||||
detail = paymentDetail?.toResponse(),
|
detail = paymentDetail?.toResponse(),
|
||||||
cancel = cancelDetail?.toResponse()
|
cancel = cancelDetail?.toResponse()
|
||||||
).also {
|
)
|
||||||
log.info { "[findDetailByReservationId] 예약 결제 정보 조회 완료: reservationId=$reservationId" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findByReservationIdOrThrow(reservationId: Long): PaymentEntity {
|
private fun findByReservationIdOrThrow(reservationId: Long): PaymentEntity {
|
||||||
log.debug { "[findByReservationIdOrThrow] 결제 정보 조회 시작: reservationId=: $reservationId" }
|
log.info { "[findByReservationIdOrThrow] 결제 정보 조회 시작: reservationId=: $reservationId" }
|
||||||
|
|
||||||
return paymentRepository.findByReservationId(reservationId)
|
return paymentRepository.findByReservationId(reservationId)
|
||||||
?.also { log.info { "[findByReservationIdOrThrow] 결제 정보 조회 완료: reservationId=$reservationId, paymentId=${it.id}" } }
|
?.also { log.info { "[findByReservationIdOrThrow] 결제 정보 조회 완료: reservationId=$reservationId, paymentId=${it.id}" } }
|
||||||
@ -116,7 +114,7 @@ class PaymentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findByReservationIdOrNull(reservationId: Long): PaymentEntity? {
|
private fun findByReservationIdOrNull(reservationId: Long): PaymentEntity? {
|
||||||
log.debug { "[findByReservationIdOrThrow] 결제 정보 조회 시작: reservationId=: $reservationId" }
|
log.info { "[findByReservationIdOrThrow] 결제 정보 조회 시작: reservationId=: $reservationId" }
|
||||||
|
|
||||||
return paymentRepository.findByReservationId(reservationId)
|
return paymentRepository.findByReservationId(reservationId)
|
||||||
.also {
|
.also {
|
||||||
@ -129,7 +127,7 @@ class PaymentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findDetailByPaymentIdOrNull(paymentId: Long): PaymentDetailEntity? {
|
private fun findDetailByPaymentIdOrNull(paymentId: Long): PaymentDetailEntity? {
|
||||||
log.debug { "[findDetailByPaymentIdOrThrow] 결제 상세 정보 조회 시작: paymentId=$paymentId" }
|
log.info { "[findDetailByPaymentIdOrThrow] 결제 상세 정보 조회 시작: paymentId=$paymentId" }
|
||||||
|
|
||||||
return paymentDetailRepository.findByPaymentId(paymentId).also {
|
return paymentDetailRepository.findByPaymentId(paymentId).also {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
@ -141,7 +139,7 @@ class PaymentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findCancelByPaymentIdOrNull(paymentId: Long): CanceledPaymentEntity? {
|
private fun findCancelByPaymentIdOrNull(paymentId: Long): CanceledPaymentEntity? {
|
||||||
log.debug { "[findDetailByReservationId] 취소 결제 정보 조회 시작: paymentId=${paymentId}" }
|
log.info { "[findDetailByReservationId] 취소 결제 정보 조회 시작: paymentId=${paymentId}" }
|
||||||
|
|
||||||
return canceledPaymentRepository.findByPaymentId(paymentId).also {
|
return canceledPaymentRepository.findByPaymentId(paymentId).also {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
|
|||||||
@ -29,16 +29,20 @@ class PaymentEventListener(
|
|||||||
fun handlePaymentEvent(event: PaymentEvent) {
|
fun handlePaymentEvent(event: PaymentEvent) {
|
||||||
val reservationId = event.reservationId
|
val reservationId = event.reservationId
|
||||||
|
|
||||||
log.debug { "[handlePaymentEvent] 결제 정보 저장 이벤트 수신: reservationId=${reservationId}, paymentKey=${event.paymentKey}" }
|
log.info { "[handlePaymentEvent] 결제 정보 저장 이벤트 수신: reservationId=${reservationId}, paymentKey=${event.paymentKey}" }
|
||||||
|
|
||||||
val paymentId = idGenerator.create()
|
val paymentId = idGenerator.create()
|
||||||
val paymentEntity: PaymentEntity = event.toEntity(paymentId)
|
val paymentEntity: PaymentEntity = event.toEntity(paymentId)
|
||||||
paymentRepository.save(paymentEntity)
|
paymentRepository.save(paymentEntity).also {
|
||||||
|
log.info { "[handlePaymentEvent] 결제 정보 저장 완료: paymentId=${paymentId}" }
|
||||||
|
}
|
||||||
|
|
||||||
val paymentDetailId = idGenerator.create()
|
val paymentDetailId = idGenerator.create()
|
||||||
val paymentDetailEntity: PaymentDetailEntity = event.toDetailEntity(id = paymentDetailId, paymentId = paymentId)
|
val paymentDetailEntity: PaymentDetailEntity = event.toDetailEntity(id = paymentDetailId, paymentId = paymentId)
|
||||||
paymentDetailRepository.save(paymentDetailEntity)
|
paymentDetailRepository.save(paymentDetailEntity).also {
|
||||||
|
log.info { "[handlePaymentEvent] 결제 상세 저장 완료: paymentDetailId=${paymentDetailId}" }
|
||||||
|
}
|
||||||
|
|
||||||
log.info { "[handlePaymentEvent] 결제 정보 저장 이벤트 처리 완료: reservationId=${reservationId}, paymentId=${paymentId}, paymentDetailId=${paymentDetailId}" }
|
log.info { "[handlePaymentEvent] 결제 정보 저장 이벤트 처리 완료" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class TosspayClient(
|
|||||||
amount: Int,
|
amount: Int,
|
||||||
): PaymentGatewayResponse {
|
): PaymentGatewayResponse {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
log.debug { "[TosspayClient.confirm] 결제 승인 요청: paymentKey=$paymentKey, orderId=$orderId, amount=$amount" }
|
log.info { "[TosspayClient.confirm] 결제 승인 요청: paymentKey=$paymentKey, orderId=$orderId, amount=$amount" }
|
||||||
|
|
||||||
return confirmClient.request(paymentKey, orderId, amount)
|
return confirmClient.request(paymentKey, orderId, amount)
|
||||||
.also {
|
.also {
|
||||||
@ -47,7 +47,7 @@ class TosspayClient(
|
|||||||
cancelReason: String
|
cancelReason: String
|
||||||
): PaymentGatewayCancelResponse {
|
): PaymentGatewayCancelResponse {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
log.debug { "[TosspayClient.cancel] 결제 취소 요청: paymentKey=$paymentKey, amount=$amount, cancelReason=$cancelReason" }
|
log.info { "[TosspayClient.cancel] 결제 취소 요청: paymentKey=$paymentKey, amount=$amount, cancelReason=$cancelReason" }
|
||||||
|
|
||||||
return cancelClient.request(paymentKey, amount, cancelReason).also {
|
return cancelClient.request(paymentKey, amount, cancelReason).also {
|
||||||
log.info { "[TosspayClient.cancel] 결제 취소 완료: duration_ms=${System.currentTimeMillis() - startTime}ms, paymentKey=$paymentKey" }
|
log.info { "[TosspayClient.cancel] 결제 취소 완료: duration_ms=${System.currentTimeMillis() - startTime}ms, paymentKey=$paymentKey" }
|
||||||
|
|||||||
@ -17,7 +17,7 @@ class RegionService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun readAllSido(): SidoListResponse {
|
fun readAllSido(): SidoListResponse {
|
||||||
log.debug { "[readAllSido] 모든 시/도 조회 시작" }
|
log.info { "[readAllSido] 모든 시/도 조회 시작" }
|
||||||
val result: List<Pair<String, String>> = regionRepository.readAllSido()
|
val result: List<Pair<String, String>> = regionRepository.readAllSido()
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
@ -32,7 +32,7 @@ class RegionService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findSigunguBySido(sidoCode: String): SigunguListResponse {
|
fun findSigunguBySido(sidoCode: String): SigunguListResponse {
|
||||||
log.debug { "[findSigunguBySido] 시/군/구 조회 시작: sidoCode=${sidoCode}" }
|
log.info { "[findSigunguBySido] 시/군/구 조회 시작: sidoCode=${sidoCode}" }
|
||||||
val result: List<Pair<String, String>> = regionRepository.findAllSigunguBySido(sidoCode)
|
val result: List<Pair<String, String>> = regionRepository.findAllSigunguBySido(sidoCode)
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
@ -47,7 +47,7 @@ class RegionService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findRegionCode(sidoCode: String, sigunguCode: String): RegionCodeResponse {
|
fun findRegionCode(sidoCode: String, sigunguCode: String): RegionCodeResponse {
|
||||||
log.debug { "[findRegionCode] 지역 코드 조회 시작: sidoCode=${sidoCode} / sigunguCode=${sigunguCode}" }
|
log.info { "[findRegionCode] 지역 코드 조회 시작: sidoCode=${sidoCode} / sigunguCode=${sigunguCode}" }
|
||||||
|
|
||||||
return regionRepository.findRegionCode(sidoCode, sigunguCode)?.let {
|
return regionRepository.findRegionCode(sidoCode, sigunguCode)?.let {
|
||||||
log.info { "[findRegionCode] 지역 코드 조회 완료: code=${it} sidoCode=${sidoCode} / sigunguCode=${sigunguCode}" }
|
log.info { "[findRegionCode] 지역 코드 조회 완료: code=${it} sidoCode=${sidoCode} / sigunguCode=${sigunguCode}" }
|
||||||
@ -60,7 +60,7 @@ class RegionService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findRegionInfo(regionCode: String): RegionInfoResponse {
|
fun findRegionInfo(regionCode: String): RegionInfoResponse {
|
||||||
log.debug { "[findRegionInfo] 지역 정보 조회 시작: regionCode=${regionCode}" }
|
log.info { "[findRegionInfo] 지역 정보 조회 시작: regionCode=${regionCode}" }
|
||||||
|
|
||||||
return regionRepository.findByCode(regionCode)?.let {
|
return regionRepository.findByCode(regionCode)?.let {
|
||||||
log.info { "[findRegionInfo] 지역 정보 조회 완료: code=${it} regionCode=${regionCode}" }
|
log.info { "[findRegionInfo] 지역 정보 조회 완료: code=${it} regionCode=${regionCode}" }
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class ReservationService(
|
|||||||
user: CurrentUserContext,
|
user: CurrentUserContext,
|
||||||
request: PendingReservationCreateRequest
|
request: PendingReservationCreateRequest
|
||||||
): PendingReservationCreateResponse {
|
): PendingReservationCreateResponse {
|
||||||
log.debug { "[createPendingReservation] Pending 예약 생성 시작: schedule=${request.scheduleId}" }
|
log.info { "[createPendingReservation] Pending 예약 생성 시작: schedule=${request.scheduleId}" }
|
||||||
|
|
||||||
run {
|
run {
|
||||||
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(request.scheduleId)
|
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(request.scheduleId)
|
||||||
@ -64,7 +64,7 @@ class ReservationService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun confirmReservation(id: Long) {
|
fun confirmReservation(id: Long) {
|
||||||
log.debug { "[confirmReservation] Pending 예약 확정 시작: reservationId=${id}" }
|
log.info { "[confirmReservation] Pending 예약 확정 시작: reservationId=${id}" }
|
||||||
val reservation: ReservationEntity = findOrThrow(id)
|
val reservation: ReservationEntity = findOrThrow(id)
|
||||||
|
|
||||||
run {
|
run {
|
||||||
@ -81,7 +81,7 @@ class ReservationService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun cancelReservation(user: CurrentUserContext, reservationId: Long, request: ReservationCancelRequest) {
|
fun cancelReservation(user: CurrentUserContext, reservationId: Long, request: ReservationCancelRequest) {
|
||||||
log.debug { "[cancelReservation] 예약 취소 시작: userId=${user.id}, reservationId=${reservationId}" }
|
log.info { "[cancelReservation] 예약 취소 시작: userId=${user.id}, reservationId=${reservationId}" }
|
||||||
|
|
||||||
val reservation: ReservationEntity = findOrThrow(reservationId)
|
val reservation: ReservationEntity = findOrThrow(reservationId)
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ class ReservationService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findAllUserReservationOverview(user: CurrentUserContext): ReservationOverviewListResponse {
|
fun findAllUserReservationOverview(user: CurrentUserContext): ReservationOverviewListResponse {
|
||||||
log.debug { "[findSummaryByMemberId] 예약 조회 시작: userId=${user.id}" }
|
log.info { "[findSummaryByMemberId] 예약 조회 시작: userId=${user.id}" }
|
||||||
|
|
||||||
val reservations: List<ReservationEntity> = reservationRepository.findAllByUserIdAndStatusIsIn(
|
val reservations: List<ReservationEntity> = reservationRepository.findAllByUserIdAndStatusIsIn(
|
||||||
userId = user.id,
|
userId = user.id,
|
||||||
@ -125,7 +125,7 @@ class ReservationService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findDetailById(id: Long): ReservationAdditionalResponse {
|
fun findDetailById(id: Long): ReservationAdditionalResponse {
|
||||||
log.debug { "[findDetailById] 예약 상세 조회 시작: reservationId=${id}" }
|
log.info { "[findDetailById] 예약 상세 조회 시작: reservationId=${id}" }
|
||||||
|
|
||||||
val reservation: ReservationEntity = findOrThrow(id)
|
val reservation: ReservationEntity = findOrThrow(id)
|
||||||
val user: UserContactResponse = userService.findContactById(reservation.userId)
|
val user: UserContactResponse = userService.findContactById(reservation.userId)
|
||||||
@ -141,7 +141,7 @@ class ReservationService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findStatusWithLock(id: Long): ReservationStateResponse {
|
fun findStatusWithLock(id: Long): ReservationStateResponse {
|
||||||
log.debug { "[findStatusWithLock] 예약 LOCK + 상태 조회 시작: reservationId=${id}" }
|
log.info { "[findStatusWithLock] 예약 LOCK + 상태 조회 시작: reservationId=${id}" }
|
||||||
|
|
||||||
return reservationRepository.findByIdForUpdate(id)?.let {
|
return reservationRepository.findByIdForUpdate(id)?.let {
|
||||||
log.info { "[findStatusWithLock] 예약 LOCK + 상태 조회 완료: reservationId=${id}" }
|
log.info { "[findStatusWithLock] 예약 LOCK + 상태 조회 완료: reservationId=${id}" }
|
||||||
@ -154,7 +154,7 @@ class ReservationService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun markInProgress(reservationId: Long) {
|
fun markInProgress(reservationId: Long) {
|
||||||
log.debug { "[markInProgress] 예약 상태 ${ReservationStatus.PAYMENT_IN_PROGRESS} 변경 시작." }
|
log.info { "[markInProgress] 예약 상태 ${ReservationStatus.PAYMENT_IN_PROGRESS} 변경 시작." }
|
||||||
|
|
||||||
findOrThrow(reservationId).apply {
|
findOrThrow(reservationId).apply {
|
||||||
this.status = ReservationStatus.PAYMENT_IN_PROGRESS
|
this.status = ReservationStatus.PAYMENT_IN_PROGRESS
|
||||||
@ -164,7 +164,7 @@ class ReservationService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findOrThrow(id: Long): ReservationEntity {
|
private fun findOrThrow(id: Long): ReservationEntity {
|
||||||
log.debug { "[findOrThrow] 예약 조회 시작: reservationId=${id}" }
|
log.info { "[findOrThrow] 예약 조회 시작: reservationId=${id}" }
|
||||||
|
|
||||||
return reservationRepository.findByIdOrNull(id)
|
return reservationRepository.findByIdOrNull(id)
|
||||||
?.also { log.info { "[findOrThrow] 예약 조회 완료: reservationId=${id}" } }
|
?.also { log.info { "[findOrThrow] 예약 조회 완료: reservationId=${id}" } }
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
package com.sangdol.roomescape.reservation.business
|
package com.sangdol.roomescape.reservation.business
|
||||||
|
|
||||||
import com.sangdol.common.utils.KoreaDateTime
|
import com.sangdol.common.utils.KoreaDateTime
|
||||||
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
|
import com.sangdol.common.utils.toKoreaDateTime
|
||||||
import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
|
import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
|
||||||
import com.sangdol.roomescape.reservation.exception.ReservationException
|
import com.sangdol.roomescape.reservation.exception.ReservationException
|
||||||
|
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
|
||||||
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
|
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeInfoResponse
|
import com.sangdol.roomescape.theme.dto.ThemeInfoResponse
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
import java.time.Instant
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class ReservationEventListener(
|
|||||||
fun handleReservationConfirmEvent(event: ReservationConfirmEvent) {
|
fun handleReservationConfirmEvent(event: ReservationConfirmEvent) {
|
||||||
val reservationId = event.reservationId
|
val reservationId = event.reservationId
|
||||||
|
|
||||||
log.debug { "[handleReservationConfirmEvent] 예약 확정 이벤트 수신: reservationId=${reservationId}" }
|
log.info { "[handleReservationConfirmEvent] 예약 확정 이벤트 수신: reservationId=${reservationId}" }
|
||||||
val modifiedRows = reservationRepository.confirmReservation(Instant.now(), reservationId)
|
val modifiedRows = reservationRepository.confirmReservation(Instant.now(), reservationId)
|
||||||
|
|
||||||
if (modifiedRows == 0) {
|
if (modifiedRows == 0) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleReposi
|
|||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling
|
||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
@ -14,6 +15,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@EnableScheduling
|
||||||
class IncompletedReservationScheduler(
|
class IncompletedReservationScheduler(
|
||||||
private val scheduleRepository: ScheduleRepository,
|
private val scheduleRepository: ScheduleRepository,
|
||||||
private val reservationRepository: ReservationRepository
|
private val reservationRepository: ReservationRepository
|
||||||
@ -22,10 +24,10 @@ class IncompletedReservationScheduler(
|
|||||||
@Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES)
|
@Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES)
|
||||||
@Transactional
|
@Transactional
|
||||||
fun processExpiredHoldSchedule() {
|
fun processExpiredHoldSchedule() {
|
||||||
log.debug { "[processExpiredHoldSchedule] 만료 시간이 지난 ${ScheduleStatus.HOLD} 상태의 일정 재활성화 시작" }
|
log.info { "[processExpiredHoldSchedule] 만료 시간이 지난 ${ScheduleStatus.HOLD} 상태의 일정 재활성화 시작" }
|
||||||
|
|
||||||
val targets: List<Long> = scheduleRepository.findAllExpiredHeldSchedules(Instant.now()).also {
|
val targets: List<Long> = scheduleRepository.findAllExpiredHeldSchedules(Instant.now()).also {
|
||||||
log.debug { "[processExpiredHoldSchedule] ${it.size} 개의 일정 조회 완료" }
|
log.info { "[processExpiredHoldSchedule] ${it.size} 개의 일정 조회 완료" }
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleRepository.releaseHeldSchedules(targets).also {
|
scheduleRepository.releaseHeldSchedules(targets).also {
|
||||||
@ -36,7 +38,7 @@ class IncompletedReservationScheduler(
|
|||||||
@Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES)
|
@Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES)
|
||||||
@Transactional
|
@Transactional
|
||||||
fun processExpiredReservation() {
|
fun processExpiredReservation() {
|
||||||
log.debug { "[processExpiredReservation] 결제되지 않은 예약 만료 처리 시작" }
|
log.info { "[processExpiredReservation] 결제되지 않은 예약 만료 처리 시작" }
|
||||||
|
|
||||||
val targets: List<Long> = reservationRepository.findAllExpiredReservation().also {
|
val targets: List<Long> = reservationRepository.findAllExpiredReservation().also {
|
||||||
log.info { "[processExpiredReservation] ${it.size} 개의 예약 조회 완료" }
|
log.info { "[processExpiredReservation] ${it.size} 개의 예약 조회 완료" }
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class AdminScheduleService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun searchSchedules(storeId: Long, date: LocalDate?, themeId: Long?): AdminScheduleSummaryListResponse {
|
fun searchSchedules(storeId: Long, date: LocalDate?, themeId: Long?): AdminScheduleSummaryListResponse {
|
||||||
log.debug { "[searchSchedules] 일정 검색 시작: storeId=$storeId, date=$date, themeId=$themeId" }
|
log.info { "[searchSchedules] 일정 검색 시작: storeId=$storeId, date=$date, themeId=$themeId" }
|
||||||
|
|
||||||
val searchDate = date ?: KoreaDate.today()
|
val searchDate = date ?: KoreaDate.today()
|
||||||
|
|
||||||
@ -44,12 +44,14 @@ class AdminScheduleService(
|
|||||||
.sortedBy { it.time }
|
.sortedBy { it.time }
|
||||||
|
|
||||||
return schedules.toAdminSummaryResponse()
|
return schedules.toAdminSummaryResponse()
|
||||||
.also { log.info { "[searchSchedules] ${it.schedules.size} 개의 일정 조회 완료" } }
|
.also {
|
||||||
|
log.info { "[searchSchedules] ${it.schedules.size} 개의 일정 조회 완료" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findScheduleAudit(id: Long): AuditingInfo {
|
fun findScheduleAudit(id: Long): AuditingInfo {
|
||||||
log.debug { "[findDetail] 일정 감사 정보 조회 시작: id=$id" }
|
log.info { "[findDetail] 일정 감사 정보 조회 시작: id=$id" }
|
||||||
|
|
||||||
val schedule: ScheduleEntity = findOrThrow(id)
|
val schedule: ScheduleEntity = findOrThrow(id)
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ class AdminScheduleService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun createSchedule(storeId: Long, request: ScheduleCreateRequest): ScheduleCreateResponse {
|
fun createSchedule(storeId: Long, request: ScheduleCreateRequest): ScheduleCreateResponse {
|
||||||
log.debug { "[createSchedule] 일정 생성 시작: storeId=${storeId}, date=${request.date}, time=${request.time}, themeId=${request.themeId}" }
|
log.info { "[createSchedule] 일정 생성 시작: storeId=${storeId}, date=${request.date}, time=${request.time}, themeId=${request.themeId}" }
|
||||||
|
|
||||||
scheduleValidator.validateCanCreate(storeId, request)
|
scheduleValidator.validateCanCreate(storeId, request)
|
||||||
|
|
||||||
@ -77,12 +79,14 @@ class AdminScheduleService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ScheduleCreateResponse(schedule.id)
|
return ScheduleCreateResponse(schedule.id)
|
||||||
.also { log.info { "[createSchedule] 일정 생성 완료: id=${it.id}" } }
|
.also {
|
||||||
|
log.info { "[createSchedule] 일정 생성 완료: id=${it.id}" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateSchedule(id: Long, request: ScheduleUpdateRequest) {
|
fun updateSchedule(id: Long, request: ScheduleUpdateRequest) {
|
||||||
log.debug { "[updateSchedule] 일정 수정 시작: id=$id, request=${request}" }
|
log.info { "[updateSchedule] 일정 수정 시작: id=$id, request=${request}" }
|
||||||
|
|
||||||
if (request.isAllParamsNull()) {
|
if (request.isAllParamsNull()) {
|
||||||
log.info { "[updateSchedule] 일정 변경 사항 없음: id=$id" }
|
log.info { "[updateSchedule] 일정 변경 사항 없음: id=$id" }
|
||||||
@ -100,7 +104,7 @@ class AdminScheduleService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun deleteSchedule(id: Long) {
|
fun deleteSchedule(id: Long) {
|
||||||
log.debug { "[deleteSchedule] 일정 삭제 시작: id=$id" }
|
log.info { "[deleteSchedule] 일정 삭제 시작: id=$id" }
|
||||||
|
|
||||||
val schedule: ScheduleEntity = findOrThrow(id).also {
|
val schedule: ScheduleEntity = findOrThrow(id).also {
|
||||||
scheduleValidator.validateCanDelete(it)
|
scheduleValidator.validateCanDelete(it)
|
||||||
@ -112,7 +116,7 @@ class AdminScheduleService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findOrThrow(id: Long): ScheduleEntity {
|
private fun findOrThrow(id: Long): ScheduleEntity {
|
||||||
log.debug { "[findOrThrow] 일정 조회 시작: id=$id" }
|
log.info { "[findOrThrow] 일정 조회 시작: id=$id" }
|
||||||
|
|
||||||
return scheduleRepository.findByIdOrNull(id)
|
return scheduleRepository.findByIdOrNull(id)
|
||||||
?.also { log.info { "[findOrThrow] 일정 조회 완료: id=$id" } }
|
?.also { log.info { "[findOrThrow] 일정 조회 완료: id=$id" } }
|
||||||
|
|||||||
@ -30,12 +30,13 @@ class ScheduleService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getStoreScheduleByDate(storeId: Long, date: LocalDate): ScheduleWithThemeListResponse {
|
fun getStoreScheduleByDate(storeId: Long, date: LocalDate): ScheduleWithThemeListResponse {
|
||||||
log.debug { "[getStoreScheduleByDate] 매장 일정 조회 시작: storeId=${storeId}, date=$date" }
|
log.info { "[getStoreScheduleByDate] 매장 일정 조회: storeId=${storeId}, date=$date" }
|
||||||
|
|
||||||
val currentDate: LocalDate = KoreaDate.today()
|
val currentDate: LocalDate = KoreaDate.today()
|
||||||
val currentTime: LocalTime = KoreaTime.now()
|
val currentTime: LocalTime = KoreaTime.now()
|
||||||
|
|
||||||
if (date.isBefore(currentDate)) {
|
if (date.isBefore(currentDate)) {
|
||||||
|
log.warn { "[getStoreScheduleByDate] 이전 날짜 선택으로 인한 실패: date=${date}" }
|
||||||
throw ScheduleException(ScheduleErrorCode.PAST_DATE_TIME)
|
throw ScheduleException(ScheduleErrorCode.PAST_DATE_TIME)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +44,6 @@ class ScheduleService(
|
|||||||
scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, date)
|
scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, date)
|
||||||
.filter { it.date.isAfter(currentDate) || it.time.isAfter(currentTime) }
|
.filter { it.date.isAfter(currentDate) || it.time.isAfter(currentTime) }
|
||||||
|
|
||||||
|
|
||||||
return schedules.toResponseWithTheme()
|
return schedules.toResponseWithTheme()
|
||||||
.also {
|
.also {
|
||||||
log.info { "[getStoreScheduleByDate] storeId=${storeId}, date=$date 인 ${it.schedules.size}개 일정 조회 완료" }
|
log.info { "[getStoreScheduleByDate] storeId=${storeId}, date=$date 인 ${it.schedules.size}개 일정 조회 완료" }
|
||||||
@ -52,7 +52,7 @@ class ScheduleService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun holdSchedule(id: Long) {
|
fun holdSchedule(id: Long) {
|
||||||
log.debug { "[holdSchedule] 일정 Holding 시작: id=$id" }
|
log.info { "[holdSchedule] 일정 Holding 시작: id=$id" }
|
||||||
|
|
||||||
val schedule = findForUpdateOrThrow(id).also {
|
val schedule = findForUpdateOrThrow(id).also {
|
||||||
scheduleValidator.validateCanHold(it)
|
scheduleValidator.validateCanHold(it)
|
||||||
@ -69,7 +69,7 @@ class ScheduleService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findStateWithLock(id: Long): ScheduleStateResponse {
|
fun findStateWithLock(id: Long): ScheduleStateResponse {
|
||||||
log.debug { "[findDateTimeById] 일정 개요 조회 시작 : id=$id" }
|
log.info { "[findDateTimeById] 일정 개요 조회 시작 : id=$id" }
|
||||||
|
|
||||||
val schedule: ScheduleEntity = scheduleRepository.findByIdForUpdate(id)
|
val schedule: ScheduleEntity = scheduleRepository.findByIdForUpdate(id)
|
||||||
?: run {
|
?: run {
|
||||||
@ -95,7 +95,7 @@ class ScheduleService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun changeStatus(scheduleId: Long, currentStatus: ScheduleStatus, changeStatus: ScheduleStatus) {
|
fun changeStatus(scheduleId: Long, currentStatus: ScheduleStatus, changeStatus: ScheduleStatus) {
|
||||||
log.debug { "[reserveSchedule] 일정 상태 변경 시작: id=${scheduleId}, currentStatus=${currentStatus}, changeStatus=${changeStatus}" }
|
log.info { "[reserveSchedule] 일정 상태 변경 시작: id=${scheduleId}, currentStatus=${currentStatus}, changeStatus=${changeStatus}" }
|
||||||
|
|
||||||
scheduleRepository.changeStatus(scheduleId, currentStatus, changeStatus).also {
|
scheduleRepository.changeStatus(scheduleId, currentStatus, changeStatus).also {
|
||||||
log.info { "[reserveSchedule] 일정 상태 변경 완료: id=${scheduleId}, currentStatus=${currentStatus}, changeStatus=${changeStatus}" }
|
log.info { "[reserveSchedule] 일정 상태 변경 완료: id=${scheduleId}, currentStatus=${currentStatus}, changeStatus=${changeStatus}" }
|
||||||
@ -103,7 +103,7 @@ class ScheduleService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findForUpdateOrThrow(id: Long): ScheduleEntity {
|
private fun findForUpdateOrThrow(id: Long): ScheduleEntity {
|
||||||
log.debug { "[findForUpdateOrThrow] 일정 LOCK + 조회 시작: id=$id" }
|
log.info { "[findForUpdateOrThrow] 일정 LOCK + 조회 시작: id=$id" }
|
||||||
|
|
||||||
return scheduleRepository.findByIdForUpdate(id)
|
return scheduleRepository.findByIdForUpdate(id)
|
||||||
?.also { log.info { "[findForUpdateOrThrow] 일정 조회 완료: id=$id" } }
|
?.also { log.info { "[findForUpdateOrThrow] 일정 조회 완료: id=$id" } }
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class ScheduleValidator(
|
|||||||
|
|
||||||
private fun validateAlreadyExists(storeId: Long, date: LocalDate, themeId: Long, time: LocalTime) {
|
private fun validateAlreadyExists(storeId: Long, date: LocalDate, themeId: Long, time: LocalTime) {
|
||||||
if (scheduleRepository.existsDuplicate(storeId, date, themeId, time)) {
|
if (scheduleRepository.existsDuplicate(storeId, date, themeId, time)) {
|
||||||
log.debug {
|
log.info {
|
||||||
"[validateAlreadyExists] 동일한 날짜, 테마, 시간 존재로 인한 실패: date=${date} / themeId=${themeId} / time=${time}"
|
"[validateAlreadyExists] 동일한 날짜, 테마, 시간 존재로 인한 실패: date=${date} / themeId=${themeId} / time=${time}"
|
||||||
}
|
}
|
||||||
throw ScheduleException(ScheduleErrorCode.SCHEDULE_ALREADY_EXISTS)
|
throw ScheduleException(ScheduleErrorCode.SCHEDULE_ALREADY_EXISTS)
|
||||||
@ -71,7 +71,7 @@ class ScheduleValidator(
|
|||||||
val inputDateTime = LocalDateTime.of(date, time).truncatedTo(ChronoUnit.MINUTES)
|
val inputDateTime = LocalDateTime.of(date, time).truncatedTo(ChronoUnit.MINUTES)
|
||||||
|
|
||||||
if (inputDateTime.isBefore(now)) {
|
if (inputDateTime.isBefore(now)) {
|
||||||
log.debug {
|
log.info {
|
||||||
"[validateDateTime] 이전 시간 선택으로 인한 실패: date=${date} / time=${time}"
|
"[validateDateTime] 이전 시간 선택으로 인한 실패: date=${date} / time=${time}"
|
||||||
}
|
}
|
||||||
throw ScheduleException(ScheduleErrorCode.PAST_DATE_TIME)
|
throw ScheduleException(ScheduleErrorCode.PAST_DATE_TIME)
|
||||||
@ -82,7 +82,7 @@ class ScheduleValidator(
|
|||||||
scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, date, themeId)
|
scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, date, themeId)
|
||||||
.firstOrNull { it.containsTime(time) }
|
.firstOrNull { it.containsTime(time) }
|
||||||
?.let {
|
?.let {
|
||||||
log.debug { "[validateTimeNotConflict] 시간이 겹치는 일정 존재: conflictSchedule(Id=${it.id}, time=${it.time}~${it.getEndAt()})" }
|
log.info { "[validateTimeNotConflict] 시간이 겹치는 일정 존재: conflictSchedule(Id=${it.id}, time=${it.time}~${it.getEndAt()})" }
|
||||||
throw ScheduleException(ScheduleErrorCode.SCHEDULE_TIME_CONFLICT)
|
throw ScheduleException(ScheduleErrorCode.SCHEDULE_TIME_CONFLICT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ class StoreService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getDetail(id: Long): StoreDetailResponse {
|
fun getDetail(id: Long): StoreDetailResponse {
|
||||||
log.debug { "[getDetail] 매장 상세 조회 시작: id=${id}" }
|
log.info { "[getDetail] 매장 상세 조회 시작: id=${id}" }
|
||||||
|
|
||||||
val store: StoreEntity = findOrThrow(id)
|
val store: StoreEntity = findOrThrow(id)
|
||||||
val region = regionService.findRegionInfo(store.regionCode)
|
val region = regionService.findRegionInfo(store.regionCode)
|
||||||
@ -47,7 +47,7 @@ class StoreService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun register(request: StoreRegisterRequest): StoreRegisterResponse {
|
fun register(request: StoreRegisterRequest): StoreRegisterResponse {
|
||||||
log.debug { "[register] 매장 등록 시작: name=${request.name}" }
|
log.info { "[register] 매장 등록 시작: name=${request.name}" }
|
||||||
|
|
||||||
storeValidator.validateCanRegister(request)
|
storeValidator.validateCanRegister(request)
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ class StoreService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun update(id: Long, request: StoreUpdateRequest) {
|
fun update(id: Long, request: StoreUpdateRequest) {
|
||||||
log.debug { "[update] 매장 수정 시작: id=${id}, request=${request}" }
|
log.info { "[update] 매장 수정 시작: id=${id}, request=${request}" }
|
||||||
|
|
||||||
storeValidator.validateCanUpdate(request)
|
storeValidator.validateCanUpdate(request)
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class StoreService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun disableById(id: Long) {
|
fun disableById(id: Long) {
|
||||||
log.debug { "[inactive] 매장 비활성화 시작: id=${id}" }
|
log.info { "[inactive] 매장 비활성화 시작: id=${id}" }
|
||||||
|
|
||||||
findOrThrow(id).apply {
|
findOrThrow(id).apply {
|
||||||
this.disable()
|
this.disable()
|
||||||
@ -94,7 +94,7 @@ class StoreService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getAllActiveStores(sidoCode: String?, sigunguCode: String?): StoreNameListResponse {
|
fun getAllActiveStores(sidoCode: String?, sigunguCode: String?): StoreNameListResponse {
|
||||||
log.debug { "[getAllActiveStores] 전체 매장 조회 시작" }
|
log.info { "[getAllActiveStores] 전체 매장 조회 시작" }
|
||||||
|
|
||||||
val regionCode: String? = when {
|
val regionCode: String? = when {
|
||||||
sidoCode == null && sigunguCode != null -> throw StoreException(StoreErrorCode.SIDO_CODE_REQUIRED)
|
sidoCode == null && sigunguCode != null -> throw StoreException(StoreErrorCode.SIDO_CODE_REQUIRED)
|
||||||
@ -108,7 +108,7 @@ class StoreService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findStoreInfo(id: Long): StoreInfoResponse {
|
fun findStoreInfo(id: Long): StoreInfoResponse {
|
||||||
log.debug { "[findStoreInfo] 매장 정보 조회 시작: id=${id}" }
|
log.info { "[findStoreInfo] 매장 정보 조회 시작: id=${id}" }
|
||||||
|
|
||||||
val store: StoreEntity = findOrThrow(id)
|
val store: StoreEntity = findOrThrow(id)
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class StoreService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getAuditInfo(store: StoreEntity): AuditingInfo {
|
private fun getAuditInfo(store: StoreEntity): AuditingInfo {
|
||||||
log.debug { "[getAuditInfo] 감사 정보 조회 시작: storeId=${store.id}" }
|
log.info { "[getAuditInfo] 감사 정보 조회 시작: storeId=${store.id}" }
|
||||||
val createdBy = adminService.findOperatorOrUnknown(store.createdBy)
|
val createdBy = adminService.findOperatorOrUnknown(store.createdBy)
|
||||||
val updatedBy = adminService.findOperatorOrUnknown(store.updatedBy)
|
val updatedBy = adminService.findOperatorOrUnknown(store.updatedBy)
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ class StoreService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findOrThrow(id: Long): StoreEntity {
|
private fun findOrThrow(id: Long): StoreEntity {
|
||||||
log.debug { "[findOrThrow] 매장 조회 시작: id=${id}" }
|
log.info { "[findOrThrow] 매장 조회 시작: id=${id}" }
|
||||||
|
|
||||||
return storeRepository.findActiveStoreById(id)
|
return storeRepository.findActiveStoreById(id)
|
||||||
?.also {
|
?.also {
|
||||||
|
|||||||
@ -31,21 +31,21 @@ class StoreValidator(
|
|||||||
|
|
||||||
private fun validateDuplicateNameExist(name: String) {
|
private fun validateDuplicateNameExist(name: String) {
|
||||||
if (storeRepository.existsByName(name)) {
|
if (storeRepository.existsByName(name)) {
|
||||||
log.debug { "[StoreValidator.validateDuplicateNameExist] 이름 중복: name=${name}" }
|
log.info { "[StoreValidator.validateDuplicateNameExist] 이름 중복: name=${name}" }
|
||||||
throw StoreException(StoreErrorCode.STORE_NAME_DUPLICATED)
|
throw StoreException(StoreErrorCode.STORE_NAME_DUPLICATED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateDuplicateContactExist(contact: String) {
|
private fun validateDuplicateContactExist(contact: String) {
|
||||||
if (storeRepository.existsByContact(contact)) {
|
if (storeRepository.existsByContact(contact)) {
|
||||||
log.debug { "[StoreValidator.validateDuplicateContact] 연락처 중복: contact=${contact}" }
|
log.info { "[StoreValidator.validateDuplicateContact] 연락처 중복: contact=${contact}" }
|
||||||
throw StoreException(StoreErrorCode.STORE_CONTACT_DUPLICATED)
|
throw StoreException(StoreErrorCode.STORE_CONTACT_DUPLICATED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateDuplicateAddressExist(address: String) {
|
private fun validateDuplicateAddressExist(address: String) {
|
||||||
if (storeRepository.existsByAddress(address)) {
|
if (storeRepository.existsByAddress(address)) {
|
||||||
log.debug { "[StoreValidator.validateDuplicateAddress] 주소 중복: address=${address}" }
|
log.info { "[StoreValidator.validateDuplicateAddress] 주소 중복: address=${address}" }
|
||||||
throw StoreException(StoreErrorCode.STORE_ADDRESS_DUPLICATED)
|
throw StoreException(StoreErrorCode.STORE_ADDRESS_DUPLICATED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,18 +3,22 @@ package com.sangdol.roomescape.theme.business
|
|||||||
import com.sangdol.common.persistence.IDGenerator
|
import com.sangdol.common.persistence.IDGenerator
|
||||||
import com.sangdol.roomescape.admin.business.AdminService
|
import com.sangdol.roomescape.admin.business.AdminService
|
||||||
import com.sangdol.roomescape.common.types.AuditingInfo
|
import com.sangdol.roomescape.common.types.AuditingInfo
|
||||||
import com.sangdol.roomescape.theme.dto.*
|
import com.sangdol.roomescape.theme.dto.ThemeDetailResponse
|
||||||
|
import com.sangdol.roomescape.theme.dto.ThemeSummaryListResponse
|
||||||
|
import com.sangdol.roomescape.theme.dto.ThemeNameListResponse
|
||||||
|
import com.sangdol.roomescape.theme.dto.ThemeCreateRequest
|
||||||
|
import com.sangdol.roomescape.theme.dto.ThemeCreateResponse
|
||||||
|
import com.sangdol.roomescape.theme.dto.ThemeUpdateRequest
|
||||||
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
||||||
import com.sangdol.roomescape.theme.exception.ThemeException
|
import com.sangdol.roomescape.theme.exception.ThemeException
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import com.sangdol.roomescape.theme.mapper.toDetailResponse
|
import com.sangdol.roomescape.theme.mapper.toDetailResponse
|
||||||
|
import com.sangdol.roomescape.theme.mapper.toSummaryListResponse
|
||||||
import com.sangdol.roomescape.theme.mapper.toEntity
|
import com.sangdol.roomescape.theme.mapper.toEntity
|
||||||
import com.sangdol.roomescape.theme.mapper.toNameListResponse
|
import com.sangdol.roomescape.theme.mapper.toNameListResponse
|
||||||
import com.sangdol.roomescape.theme.mapper.toSummaryListResponse
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.springframework.cache.annotation.CacheEvict
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
@ -30,7 +34,7 @@ class AdminThemeService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findThemeSummaries(): ThemeSummaryListResponse {
|
fun findThemeSummaries(): ThemeSummaryListResponse {
|
||||||
log.debug { "[findAdminThemes] 관리자 페이지에서의 테마 목록 조회 시작" }
|
log.info { "[findAdminThemes] 관리자 페이지에서의 테마 목록 조회 시작" }
|
||||||
|
|
||||||
return themeRepository.findAll()
|
return themeRepository.findAll()
|
||||||
.toSummaryListResponse()
|
.toSummaryListResponse()
|
||||||
@ -39,7 +43,7 @@ class AdminThemeService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findThemeDetail(id: Long): ThemeDetailResponse {
|
fun findThemeDetail(id: Long): ThemeDetailResponse {
|
||||||
log.debug { "[findAdminThemeDetail] 관리자 페이지에서의 테마 상세 정보 조회 시작: id=${id}" }
|
log.info { "[findAdminThemeDetail] 관리자 페이지에서의 테마 상세 정보 조회 시작: id=${id}" }
|
||||||
|
|
||||||
val theme: ThemeEntity = findOrThrow(id)
|
val theme: ThemeEntity = findOrThrow(id)
|
||||||
|
|
||||||
@ -53,7 +57,7 @@ class AdminThemeService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findActiveThemes(): ThemeNameListResponse {
|
fun findActiveThemes(): ThemeNameListResponse {
|
||||||
log.debug { "[findActiveThemes] open 상태인 모든 테마 조회 시작" }
|
log.info { "[findActiveThemes] open 상태인 모든 테마 조회 시작" }
|
||||||
|
|
||||||
return themeRepository.findActiveThemes()
|
return themeRepository.findActiveThemes()
|
||||||
.toNameListResponse()
|
.toNameListResponse()
|
||||||
@ -65,7 +69,7 @@ class AdminThemeService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun createTheme(request: ThemeCreateRequest): ThemeCreateResponse {
|
fun createTheme(request: ThemeCreateRequest): ThemeCreateResponse {
|
||||||
log.debug { "[createTheme] 테마 생성 시작: name=${request.name}" }
|
log.info { "[createTheme] 테마 생성 시작: name=${request.name}" }
|
||||||
|
|
||||||
themeValidator.validateCanCreate(request)
|
themeValidator.validateCanCreate(request)
|
||||||
|
|
||||||
@ -77,10 +81,10 @@ class AdminThemeService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheNames = ["theme-details"], key = "#id")
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun deleteTheme(id: Long) {
|
fun deleteTheme(id: Long) {
|
||||||
log.debug { "[deleteTheme] 테마 삭제 시작: id=${id}" }
|
log.info { "[deleteTheme] 테마 삭제 시작: id=${id}" }
|
||||||
|
|
||||||
val theme: ThemeEntity = findOrThrow(id)
|
val theme: ThemeEntity = findOrThrow(id)
|
||||||
|
|
||||||
@ -89,10 +93,9 @@ class AdminThemeService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheNames = ["theme-details"], key = "#id")
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateTheme(id: Long, request: ThemeUpdateRequest) {
|
fun updateTheme(id: Long, request: ThemeUpdateRequest) {
|
||||||
log.debug { "[updateTheme] 테마 수정 시작: id=${id}, request=${request}" }
|
log.info { "[updateTheme] 테마 수정 시작: id=${id}, request=${request}" }
|
||||||
|
|
||||||
if (request.isAllParamsNull()) {
|
if (request.isAllParamsNull()) {
|
||||||
log.info { "[updateTheme] 테마 변경 사항 없음: id=${id}" }
|
log.info { "[updateTheme] 테마 변경 사항 없음: id=${id}" }
|
||||||
@ -121,7 +124,7 @@ class AdminThemeService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findOrThrow(id: Long): ThemeEntity {
|
private fun findOrThrow(id: Long): ThemeEntity {
|
||||||
log.debug { "[findOrThrow] 테마 조회 시작: id=$id" }
|
log.info { "[findOrThrow] 테마 조회 시작: id=$id" }
|
||||||
|
|
||||||
return themeRepository.findByIdOrNull(id)
|
return themeRepository.findByIdOrNull(id)
|
||||||
?.also { log.info { "[findOrThrow] 테마 조회 완료: id=$id" } }
|
?.also { log.info { "[findOrThrow] 테마 조회 완료: id=$id" } }
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import com.sangdol.roomescape.theme.mapper.toInfoResponse
|
|||||||
import com.sangdol.roomescape.theme.mapper.toListResponse
|
import com.sangdol.roomescape.theme.mapper.toListResponse
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.micrometer.core.instrument.MeterRegistry
|
|
||||||
import org.springframework.cache.annotation.Cacheable
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
@ -23,19 +21,13 @@ private val log: KLogger = KotlinLogging.logger {}
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ThemeService(
|
class ThemeService(
|
||||||
private val themeRepository: ThemeRepository,
|
private val themeRepository: ThemeRepository
|
||||||
meterRegistry: MeterRegistry
|
|
||||||
) {
|
) {
|
||||||
private val themeDetailQueryRequestCount = meterRegistry.counter("theme.detail.query.requested")
|
|
||||||
|
|
||||||
@Cacheable(cacheNames = ["theme-details"], key="#id")
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findInfoById(id: Long): ThemeInfoResponse {
|
fun findInfoById(id: Long): ThemeInfoResponse {
|
||||||
log.debug { "[findInfoById] 테마 조회 시작: id=$id" }
|
log.info { "[findInfoById] 테마 조회 시작: id=$id" }
|
||||||
|
|
||||||
val theme = themeRepository.findByIdOrNull(id)?.also {
|
val theme = themeRepository.findByIdOrNull(id) ?: run {
|
||||||
themeDetailQueryRequestCount.increment()
|
|
||||||
} ?: run {
|
|
||||||
log.warn { "[updateTheme] 테마 조회 실패: id=$id" }
|
log.warn { "[updateTheme] 테마 조회 실패: id=$id" }
|
||||||
throw ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
throw ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||||
}
|
}
|
||||||
@ -46,7 +38,7 @@ class ThemeService(
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findMostReservedThemeLastWeek(count: Int): ThemeInfoListResponse {
|
fun findMostReservedThemeLastWeek(count: Int): ThemeInfoListResponse {
|
||||||
log.debug { "[findMostReservedThemeLastWeek] 인기 테마 조회 시작: count=$count" }
|
log.info { "[findMostReservedThemeLastWeek] 인기 테마 조회 시작: count=$count" }
|
||||||
|
|
||||||
val previousWeekSunday = DateUtils.getSundayOfPreviousWeek(KoreaDate.today())
|
val previousWeekSunday = DateUtils.getSundayOfPreviousWeek(KoreaDate.today())
|
||||||
val previousWeekSaturday = previousWeekSunday.plusDays(6)
|
val previousWeekSaturday = previousWeekSunday.plusDays(6)
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class UserService(
|
|||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findCredentialsByAccount(email: String): UserLoginCredentials {
|
fun findCredentialsByAccount(email: String): UserLoginCredentials {
|
||||||
log.debug { "[findCredentialsByAccount] 회원 조회 시작: email=${email}" }
|
log.info { "[findCredentialsByAccount] 회원 조회 시작: email=${email}" }
|
||||||
|
|
||||||
return userRepository.findByEmail(email)
|
return userRepository.findByEmail(email)
|
||||||
?.let {
|
?.let {
|
||||||
@ -37,13 +37,14 @@ class UserService(
|
|||||||
it.toCredentials()
|
it.toCredentials()
|
||||||
}
|
}
|
||||||
?: run {
|
?: run {
|
||||||
|
log.info { "[findCredentialsByAccount] 회원 조회 실패" }
|
||||||
throw UserException(UserErrorCode.USER_NOT_FOUND)
|
throw UserException(UserErrorCode.USER_NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findContactById(id: Long): UserContactResponse {
|
fun findContactById(id: Long): UserContactResponse {
|
||||||
log.debug { "[findContactById] 회원 연락 정보 조회 시작: id=${id}" }
|
log.info { "[findContactById] 회원 연락 정보 조회 시작: id=${id}" }
|
||||||
|
|
||||||
val user = findOrThrow(id)
|
val user = findOrThrow(id)
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ class UserService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun signup(request: UserCreateRequest): UserCreateResponse {
|
fun signup(request: UserCreateRequest): UserCreateResponse {
|
||||||
log.debug { "[signup] 회원가입 시작: request:$request" }
|
log.info { "[signup] 회원가입 시작: request:$request" }
|
||||||
|
|
||||||
userValidator.validateCanSignup(request.email, request.phone)
|
userValidator.validateCanSignup(request.email, request.phone)
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,14 @@ class UserValidator(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
fun validateCanSignup(email: String, phone: String) {
|
fun validateCanSignup(email: String, phone: String) {
|
||||||
log.debug { "[UserValidator.validateCanSignup] 회원가입 가능 여부 검증 시작: email:$email / phone:$phone" }
|
log.info { "[UserValidator.validateCanSignup] 회원가입 가능 여부 검증 시작: email:$email / phone:$phone" }
|
||||||
|
|
||||||
if (userRepository.existsByEmail(email)) {
|
if (userRepository.existsByEmail(email)) {
|
||||||
log.debug { "[UserValidator.validateCanSignup] 중복된 이메일 입력으로 인한 실패: email:$email" }
|
log.info { "[UserValidator.validateCanSignup] 중복된 이메일 입력으로 인한 실패: email:$email" }
|
||||||
throw UserException(UserErrorCode.EMAIL_ALREADY_EXISTS)
|
throw UserException(UserErrorCode.EMAIL_ALREADY_EXISTS)
|
||||||
}
|
}
|
||||||
if (userRepository.existsByPhone(phone)) {
|
if (userRepository.existsByPhone(phone)) {
|
||||||
log.debug { "[UserValidator.validateCanSignup] 중복된 휴대폰 번호 입력으로 인한 실패: phone:$phone" }
|
log.info { "[UserValidator.validateCanSignup] 중복된 휴대폰 번호 입력으로 인한 실패: phone:$phone" }
|
||||||
throw UserException(UserErrorCode.PHONE_ALREADY_EXISTS)
|
throw UserException(UserErrorCode.PHONE_ALREADY_EXISTS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,9 +16,6 @@ spring:
|
|||||||
jdbc:
|
jdbc:
|
||||||
batch_size: ${JDBC_BATCH_SIZE:100}
|
batch_size: ${JDBC_BATCH_SIZE:100}
|
||||||
order_inserts: true
|
order_inserts: true
|
||||||
cache:
|
|
||||||
type: caffeine
|
|
||||||
cache-names: ${CACHE_NAMES:theme-details}
|
|
||||||
|
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package com.sangdol.roomescape.auth
|
package com.sangdol.roomescape.auth.business
|
||||||
|
|
||||||
import com.sangdol.roomescape.auth.business.domain.PrincipalType
|
import com.sangdol.roomescape.auth.business.domain.PrincipalType
|
||||||
import com.sangdol.roomescape.auth.infrastructure.persistence.LoginHistoryEntity
|
import com.sangdol.roomescape.auth.infrastructure.persistence.LoginHistoryEntity
|
||||||
@ -1,13 +1,12 @@
|
|||||||
package com.sangdol.roomescape.reservation
|
package com.sangdol.roomescape.reservation.business.event
|
||||||
|
|
||||||
import com.sangdol.roomescape.reservation.business.event.ReservationConfirmEvent
|
|
||||||
import com.sangdol.roomescape.reservation.business.event.ReservationEventListener
|
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||||
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
||||||
import io.kotest.assertions.assertSoftly
|
import io.kotest.assertions.assertSoftly
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
@ -6,28 +6,21 @@ import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType
|
|||||||
import com.sangdol.roomescape.auth.exception.AuthErrorCode
|
import com.sangdol.roomescape.auth.exception.AuthErrorCode
|
||||||
import com.sangdol.roomescape.supports.*
|
import com.sangdol.roomescape.supports.*
|
||||||
import com.sangdol.roomescape.supports.ThemeFixture.createRequest
|
import com.sangdol.roomescape.supports.ThemeFixture.createRequest
|
||||||
import com.sangdol.roomescape.theme.business.AdminThemeService
|
|
||||||
import com.sangdol.roomescape.theme.business.MIN_DURATION
|
import com.sangdol.roomescape.theme.business.MIN_DURATION
|
||||||
import com.sangdol.roomescape.theme.business.MIN_PARTICIPANTS
|
import com.sangdol.roomescape.theme.business.MIN_PARTICIPANTS
|
||||||
import com.sangdol.roomescape.theme.business.MIN_PRICE
|
import com.sangdol.roomescape.theme.business.MIN_PRICE
|
||||||
import com.sangdol.roomescape.theme.business.ThemeService
|
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeInfoResponse
|
|
||||||
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeUpdateRequest
|
import com.sangdol.roomescape.theme.dto.ThemeUpdateRequest
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.springframework.cache.CacheManager
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
|
|
||||||
class AdminThemeApiTest(
|
class AdminThemeApiTest(
|
||||||
private val themeRepository: ThemeRepository,
|
private val themeRepository: ThemeRepository
|
||||||
private val themeService: ThemeService,
|
|
||||||
private val cacheManager: CacheManager
|
|
||||||
) : FunSpecSpringbootTest() {
|
) : FunSpecSpringbootTest() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -489,19 +482,14 @@ class AdminThemeApiTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("정상 삭제 및 캐시 제거 확인") {
|
test("정상 삭제") {
|
||||||
val token = testAuthUtil.defaultHqAdminLogin().second
|
val token = testAuthUtil.defaultHqAdminLogin().second
|
||||||
val createdTheme = initialize("테스트를 위한 테마 생성") {
|
val createdTheme = initialize("테스트를 위한 테마 생성") {
|
||||||
dummyInitializer.createTheme()
|
dummyInitializer.createTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize("테마 캐시 추가") {
|
|
||||||
themeService.findInfoById(createdTheme.id)
|
|
||||||
cacheManager.getCache("theme-details")?.get(createdTheme.id, ThemeInfoResponse::class.java).shouldNotBeNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
token = token,
|
token = testAuthUtil.defaultHqAdminLogin().second,
|
||||||
on = {
|
on = {
|
||||||
delete("/admin/themes/${createdTheme.id}")
|
delete("/admin/themes/${createdTheme.id}")
|
||||||
},
|
},
|
||||||
@ -510,7 +498,6 @@ class AdminThemeApiTest(
|
|||||||
}
|
}
|
||||||
).also {
|
).also {
|
||||||
themeRepository.findByIdOrNull(createdTheme.id) shouldBe null
|
themeRepository.findByIdOrNull(createdTheme.id) shouldBe null
|
||||||
cacheManager.getCache("theme-details")?.get(createdTheme.id, ThemeInfoResponse::class.java) shouldBe null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +566,7 @@ class AdminThemeApiTest(
|
|||||||
|
|
||||||
val updateRequest = ThemeUpdateRequest(name = "modified")
|
val updateRequest = ThemeUpdateRequest(name = "modified")
|
||||||
|
|
||||||
test("정상 수정 및 감사 정보 & 캐시 변경 확인") {
|
test("정상 수정 및 감사 정보 변경 확인") {
|
||||||
val createdThemeId: Long = initialize("테스트를 위한 관리자1의 테마 생성") {
|
val createdThemeId: Long = initialize("테스트를 위한 관리자1의 테마 생성") {
|
||||||
runTest(
|
runTest(
|
||||||
token = testAuthUtil.defaultHqAdminLogin().second,
|
token = testAuthUtil.defaultHqAdminLogin().second,
|
||||||
@ -595,11 +582,6 @@ class AdminThemeApiTest(
|
|||||||
).extract().path("data.id")
|
).extract().path("data.id")
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize("테마 캐시 추가") {
|
|
||||||
themeService.findInfoById(createdThemeId)
|
|
||||||
cacheManager.getCache("theme-details")?.get(createdThemeId, ThemeInfoResponse::class.java).shouldNotBeNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
val (otherAdmin, otherAdminToken) = initialize("감사 정보 변경 확인을 위한 관리자2 로그인") {
|
val (otherAdmin, otherAdminToken) = initialize("감사 정보 변경 확인을 위한 관리자2 로그인") {
|
||||||
testAuthUtil.adminLogin(
|
testAuthUtil.adminLogin(
|
||||||
AdminFixture.createHqAdmin(permissionLevel = AdminPermissionLevel.WRITABLE)
|
AdminFixture.createHqAdmin(permissionLevel = AdminPermissionLevel.WRITABLE)
|
||||||
@ -622,12 +604,6 @@ class AdminThemeApiTest(
|
|||||||
|
|
||||||
updatedTheme.name shouldBe updateRequest.name
|
updatedTheme.name shouldBe updateRequest.name
|
||||||
updatedTheme.updatedBy shouldBe otherAdmin.id
|
updatedTheme.updatedBy shouldBe otherAdmin.id
|
||||||
|
|
||||||
|
|
||||||
// 캐시 제거 확인
|
|
||||||
assertSoftly(cacheManager.getCache("theme-details")?.get(createdThemeId, ThemeInfoResponse::class.java)) {
|
|
||||||
this shouldBe null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,32 +10,23 @@ import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
|||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import com.sangdol.roomescape.theme.mapper.toEntity
|
import com.sangdol.roomescape.theme.mapper.toEntity
|
||||||
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
|
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.matchers.collections.shouldContainInOrder
|
import io.kotest.matchers.collections.shouldContainInOrder
|
||||||
import io.kotest.matchers.collections.shouldHaveSize
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
import io.kotest.matchers.comparables.shouldBeLessThan
|
import io.kotest.matchers.comparables.shouldBeLessThan
|
||||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.springframework.cache.CacheManager
|
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
class ThemeApiTest(
|
class ThemeApiTest(
|
||||||
private val themeRepository: ThemeRepository,
|
private val themeRepository: ThemeRepository
|
||||||
private val cacheManager: CacheManager
|
|
||||||
) : FunSpecSpringbootTest() {
|
) : FunSpecSpringbootTest() {
|
||||||
init {
|
init {
|
||||||
context("ID로 테마 정보를 조회한다.") {
|
context("ID로 테마 정보를 조회한다.") {
|
||||||
test("정상 응답 및 캐시 저장 확인") {
|
test("정상 응답") {
|
||||||
val createdTheme: ThemeEntity = initialize("조회를 위한 테마 생성") {
|
val createdTheme: ThemeEntity = initialize("조회를 위한 테마 생성") {
|
||||||
dummyInitializer.createTheme()
|
dummyInitializer.createTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheManager.getCache("theme-details")?.get(createdTheme.id, ThemeInfoResponse::class.java).also {
|
|
||||||
it shouldBe null
|
|
||||||
}
|
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
on = {
|
on = {
|
||||||
get("/themes/${createdTheme.id}")
|
get("/themes/${createdTheme.id}")
|
||||||
@ -52,15 +43,6 @@ class ThemeApiTest(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
assertSoftly(cacheManager.getCache("theme-details")) {
|
|
||||||
this.shouldNotBeNull()
|
|
||||||
|
|
||||||
val themeFromCache = this.get(createdTheme.id, ThemeInfoResponse::class.java)
|
|
||||||
|
|
||||||
themeFromCache.shouldNotBeNull()
|
|
||||||
themeFromCache.id shouldBe createdTheme.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("테마가 없으면 실패한다.") {
|
test("테마가 없으면 실패한다.") {
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
package com.sangdol.roomescape.theme
|
|
||||||
|
|
||||||
import com.ninjasquad.springmockk.MockkBean
|
|
||||||
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
|
||||||
import com.sangdol.roomescape.supports.IDGenerator
|
|
||||||
import com.sangdol.roomescape.supports.initialize
|
|
||||||
import com.sangdol.roomescape.theme.business.ThemeService
|
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.Difficulty
|
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
|
|
||||||
class ThemeConcurrencyTest(
|
|
||||||
private val themeService: ThemeService,
|
|
||||||
@MockkBean private val themeRepository: ThemeRepository,
|
|
||||||
) : FunSpecSpringbootTest() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
test("동일한 테마에 대한 반복 조회 요청시, DB 요청은 1회만 발생한다.") {
|
|
||||||
val entity = ThemeEntity(
|
|
||||||
id = IDGenerator.create(),
|
|
||||||
name = "테스트입니다.",
|
|
||||||
description = "테스트에요!",
|
|
||||||
thumbnailUrl = "http://localhost:8080/hello",
|
|
||||||
difficulty = Difficulty.VERY_EASY,
|
|
||||||
price = 10000,
|
|
||||||
minParticipants = 3,
|
|
||||||
maxParticipants = 5,
|
|
||||||
availableMinutes = 90,
|
|
||||||
expectedMinutesFrom = 70,
|
|
||||||
expectedMinutesTo = 80,
|
|
||||||
isActive = true
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
every {
|
|
||||||
themeRepository.findByIdOrNull(entity.id)
|
|
||||||
} returns entity
|
|
||||||
|
|
||||||
initialize("캐시 등록") {
|
|
||||||
themeService.findInfoById(entity.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestCount = 64
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val latch = CountDownLatch(requestCount)
|
|
||||||
|
|
||||||
(1..requestCount).map {
|
|
||||||
async {
|
|
||||||
latch.countDown()
|
|
||||||
latch.await()
|
|
||||||
themeService.findInfoById(entity.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verify(exactly = 1) {
|
|
||||||
themeRepository.findByIdOrNull(entity.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,9 +18,6 @@ spring:
|
|||||||
init:
|
init:
|
||||||
mode: always
|
mode: always
|
||||||
schema-locations: classpath:schema/schema-mysql.sql
|
schema-locations: classpath:schema/schema-mysql.sql
|
||||||
cache:
|
|
||||||
type: caffeine
|
|
||||||
cache-names: ${CACHE_NAMES:theme-details}
|
|
||||||
|
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user