[Feature] 카카오 Gift Biz 기프티콘 발송 및 팅 거래 기록 추가 - #80
Conversation
Walkthrough기프티콘 카탈로그 동기화, 상품 조회, 토큰 암호화·설정, 주문 발송과 재시도, 기프티콘 결제 상태 및 팅 거래 기록을 추가했습니다. 관리자용 API와 화면, Kakao Giftbiz 연동, 관련 데이터베이스 마이그레이션과 테스트도 포함됩니다. Changes기프티콘 플랫폼
기존 미팅·채팅 스키마 변경
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (5)
manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql (1)
7-18: 🩺 Stability & Availability | 🔵 Trivial운영 중 테이블에 대한 blocking DDL을 온라인 단계로 분리하세요.
여러 마이그레이션에서 기존 데이터 전체 검증과 일반 인덱스 생성을 수행하므로, 배포 중 쓰기 중단 또는 긴 잠금이 발생할 수 있습니다.
manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql#L7-L18: FK와 CHECK를NOT VALID로 추가한 뒤 별도 검증 단계로 분리하세요.manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql#L20-L25: 인덱스는CONCURRENTLY사용 여부를 확인하세요.manabom/src/main/resources/db/migration/V24__add_unique_active_chat_member.sql#L1-L2: UNIQUE 인덱스의 동시 생성과 중복 데이터 사전 정리를 확인하세요.manabom/src/main/resources/db/migration/V25__add_meeting_verification_failure_notification.sql#L4-L5: 운영 테이블 규모에 따라 동시 생성을 검토하세요.manabom/src/main/resources/db/migration/V26__add_structured_system_message_fields.sql#L8-L10: FK를NOT VALID로 추가한 뒤 검증을 분리하세요.manabom/src/main/resources/db/migration/V26__add_structured_system_message_fields.sql#L12-L14: 인덱스 동시 생성을 검토하세요.manabom/src/main/resources/db/migration/V26__add_structured_system_message_fields.sql#L20-L22: FK를NOT VALID로 추가한 뒤 검증을 분리하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql` around lines 7 - 18, 운영 테이블에 대한 검증 및 인덱스 생성을 온라인 단계로 분리하세요. V22의 fk_cancellation_request_meeting_match와 chk_cancellation_request_target은 NOT VALID로 추가한 뒤 별도 VALIDATE 단계에서 검증하고, 같은 파일의 인덱스는 CONCURRENTLY 사용을 확인하세요. V24의 UNIQUE 인덱스는 중복 데이터를 사전 정리한 후 동시 생성하고, V25 인덱스는 테이블 규모에 맞춰 동시 생성을 적용하세요. V26의 두 FK는 NOT VALID 추가 후 별도 검증으로 분리하며, 해당 인덱스에는 동시 생성을 검토하세요. 영향을 받는 파일은 manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql(7-18, 20-25), manabom/src/main/resources/db/migration/V24__add_unique_active_chat_member.sql(1-2), manabom/src/main/resources/db/migration/V25__add_meeting_verification_failure_notification.sql(4-5), manabom/src/main/resources/db/migration/V26__add_structured_system_message_fields.sql(8-10, 12-14, 20-22)입니다.Source: Linters/SAST tools
manabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sql (1)
4-10: 🩺 Stability & Availability | 🔵 Trivial기존 운영 테이블에 블로킹 DDL을 적용하는 배포 전략을 통일하세요.
manabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sql#L4-L10: FK는NOT VALID후 별도 검증하고, 인덱스는CONCURRENTLY생성을 검토하세요.manabom/src/main/resources/db/migration/V31__add_gifticon_template_token.sql#L4-L6: UNIQUE 인덱스를 온라인 생성하거나, V32에서 즉시 삭제되는 경우 생성 자체를 제거하세요.manabom/src/main/resources/db/migration/V34__hold_paid_ting_for_gifticon.sql#L5-L17: CHECK 제약조건은NOT VALID추가 후 별도 검증하는 방식을 검토하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@manabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sql` around lines 4 - 10, Update manabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sql lines 4-10 to add the foreign key as NOT VALID and validate it separately, and use concurrent index creation where supported. Update manabom/src/main/resources/db/migration/V31__add_gifticon_template_token.sql lines 4-6 to create the UNIQUE index online, or remove its creation if V32 immediately drops it. Update manabom/src/main/resources/db/migration/V34__hold_paid_ting_for_gifticon.sql lines 5-17 to add CHECK constraints as NOT VALID and validate them separately.Source: Linters/SAST tools
manabom/src/main/resources/db/migration/V36__remove_gifticon_order_template_token.sql (1)
1-2: 🩺 Stability & Availability | 🔵 Trivial롤링 배포 시 컬럼 삭제 순서를 확인하세요.
DROP COLUMN은 기존GifticonOrder매핑이 해당 컬럼을 조회하는 구버전 인스턴스와 호환되지 않습니다. 먼저 컬럼을 읽지 않는 애플리케이션을 배포한 뒤 후속 마이그레이션에서 삭제하거나, 구버전 인스턴스가 없는 중단 배포인지 확인해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@manabom/src/main/resources/db/migration/V36__remove_gifticon_order_template_token.sql` around lines 1 - 2, Review the V36 migration’s DROP COLUMN operation against the deployment strategy: for rolling deployments, first deploy an application version that no longer reads encrypted_template_token, then remove the column in a subsequent migration; otherwise explicitly confirm this migration runs only during a coordinated downtime deployment with no old GifticonOrder instances.Source: Linters/SAST tools
manabom/src/main/resources/db/migration/V37__add_gifticon_order_sender_nickname.sql (1)
16-17: 🩺 Stability & Availability | 🔵 Trivial대용량 테이블의 NOT NULL 전환을 온라인 방식으로 검토하세요.
ALTER COLUMN ... SET NOT NULL은 기존 테이블을 검사하면서 강한 락을 획득할 수 있어, 주문량이나 트래픽이 많은 환경에서는 읽기·쓰기 지연을 유발할 수 있습니다. 테이블 규모를 확인하고 점검 시간대에 실행하거나, 검증 가능한 단계적 제약 적용 방식을 사용해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@manabom/src/main/resources/db/migration/V37__add_gifticon_order_sender_nickname.sql` around lines 16 - 17, Update migration V37’s gifticon_order sender_nickname constraint change to use an online, staged validation approach that avoids a long strong lock on the large table. Verify existing NULL data first, add and validate an equivalent constraint in a low-impact step, then enforce NOT NULL only through the safest supported operation for this database; otherwise schedule the direct ALTER during a maintenance window.Source: Linters/SAST tools
manabom/src/main/java/mannabom_server/manabom/infrastructure/security/crypto/AesGcmGifticonTokenCipher.java (1)
25-32: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win키 검증을 지연 대신 조기(fail-fast)로 수행하는 것을 고려
secretKey()가 매 encrypt/decrypt 호출마다 Base64 디코딩과 32바이트 검증을 반복합니다. 오버헤드 자체는 작지만, 키가 잘못 설정된 경우 애플리케이션 기동 시점이 아니라 실제로 토큰을 암복호화하는 시점(예: 기프티콘 발송 시)에야 오류가 드러납니다. 생성자 또는@PostConstruct에서 디코딩된SecretKey를 한 번만 검증·캐싱해두면 배포 초기에 설정 오류를 감지할 수 있고 반복 디코딩도 피할 수 있습니다. (키를 선택적으로 비워두는 환경이 있다면 해당 트레이드오프를 고려해 적용해 주세요.)Also applies to: 83-102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@manabom/src/main/java/mannabom_server/manabom/infrastructure/security/crypto/AesGcmGifticonTokenCipher.java` around lines 25 - 32, Update AesGcmGifticonTokenCipher to decode and validate the configured key during construction or initialization, then cache and reuse the validated SecretKey in secretKey() and the encrypt/decrypt paths. Preserve the existing optional-empty-key behavior if supported, while ensuring invalid non-empty keys fail during application startup rather than during token operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@manabom/src/main/java/mannabom_server/manabom/application/admin/dto/request/AdminConfigureGifticonTokenRequest.java`:
- Around line 16-17: Update the reason field in
AdminConfigureGifticonTokenRequest to require a non-null, non-blank value while
retaining the existing 500-character maximum validation and message. Ensure
configureTemplateToken receives only valid audit-log reasons through request
validation.
In
`@manabom/src/main/java/mannabom_server/manabom/application/currency/service/TingTransactionRecorder.java`:
- Around line 62-87: Update TingTransactionRecorder.record() to check for an
existing transaction by idempotencyKey before saving; add the required
TingTransactionRepository lookup and return the existing record when found,
otherwise save a new transaction. Ensure the persistence model enforces
uniqueness for the idempotency key while preserving the pre-save lookup flow.
In
`@manabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.java`:
- Around line 15-17: 외부 발송이 동기 처리되거나 잠금 트랜잭션을 장시간 점유하지 않도록 분리하세요.
GifticonOrderReadyEventListener.java의 requestGift 메서드는 `@Async` 또는 내구성 있는 작업 큐로 주문
ID만 위임하세요. GifticonOrderProcessor.java의 처리 흐름은 PESSIMISTIC_WRITE 잠금 하에서 완료된 요청
처리를 마친 뒤 원격 요청만 수행하고, 성공한 경우 별도 트랜잭션에서 markRequested()를 기록하도록 변경하세요.
In
`@manabom/src/main/java/mannabom_server/manabom/application/signup/service/SignupService.java`:
- Around line 8-13: Update the signup bonus recording flow in SignupService so
TingTransactionRecorder.recordEvent is invoked only when signupBonusEventTing is
greater than zero. Preserve the existing transaction details and behavior for
positive bonuses, while skipping creation of zero-amount ledger rows.
In
`@manabom/src/main/java/mannabom_server/manabom/domain/gifticon/entity/GifticonProduct.java`:
- Around line 163-165: Update GifticonProduct.isOrderableAt to require a
strictly positive tingPrice in addition to hasTemplateToken() and
isAvailableAt(now), so zero-priced products are not reported as orderable.
In
`@manabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/KakaoGiftbizTemplateMapper.java`:
- Around line 32-38: Update KakaoGiftbizTemplateMapper’s product mapping to
validate that product and product.productPrice() are present before creating the
snapshot; handle invalid templates explicitly by skipping them at the
individual-item level so GifticonCatalogService catalog synchronization
continues without passing a null price to
GifticonPriceCalculator.calculateTingPrice(int).
In
`@manabom/src/main/java/mannabom_server/manabom/infrastructure/security/crypto/AesGcmGifticonTokenCipher.java`:
- Around line 83-102: AesGcmGifticonTokenCipher의 secretKey()에서 사용하는 모든 설정 오류
메시지의 키 이름을 실제 프로퍼티 또는 대응 환경변수명인 APP_KAKAO_GIFTBIZ_TOKEN_ENCRYPTION_KEY로 통일하세요. 빈
값, 잘못된 Base64, 잘못된 키 길이 검증 메시지 모두 동일한 올바른 설정 키를 참조하도록 수정하고 나머지 검증 동작은 유지하세요.
In `@manabom/src/main/resources/application.yml`:
- Around line 94-105: Update the giftbiz.sync.enabled configuration default to
false so synchronization is disabled when KAKAO_GIFTBIZ_AUTHORIZATION is unset,
while preserving explicit environment values that enable synchronization.
In
`@manabom/src/main/resources/db/migration/V24__add_unique_active_chat_member.sql`:
- Around line 1-3: Update the partial unique index
uk_chat_members_active_room_user to filter chat_members rows with status ACTIVE,
matching the existing status default. Before creating the corrected index,
remove any existing duplicate active room/user records if the migration must
handle pre-existing duplicates.
In `@manabom/src/main/resources/db/migration/V28__create_gifticon_product.sql`:
- Around line 19-20: Update the ting_price constraint in the gifticon product
schema to require values greater than zero, aligning catalog validation with
V34__hold_paid_ting_for_gifticon.sql and the existing payment initialization
flow. Preserve the nonnegative product_price constraint unchanged.
In
`@manabom/src/main/resources/db/migration/V32__encrypt_gifticon_template_token.sql`:
- Around line 4-8: Update the V32 migration so existing plaintext values from
template_token are never retained in encrypted_template_token: do not use a
direct rename that carries the data, or clear the renamed column immediately
before any application use. Preserve the encrypted_template_token column
definition while requiring existing products to be re-registered or separately
backfilled through the encryption path.
In
`@manabom/src/main/resources/db/migration/V34__hold_paid_ting_for_gifticon.sql`:
- Around line 8-17: Update the ck_message_request_gift_payment_status CHECK
constraint so the gifticon_product_id IS NOT NULL branch explicitly requires
gift_payment_status IS NOT NULL before validating its allowed values, preventing
NULL statuses from bypassing the constraint.
---
Nitpick comments:
In
`@manabom/src/main/java/mannabom_server/manabom/infrastructure/security/crypto/AesGcmGifticonTokenCipher.java`:
- Around line 25-32: Update AesGcmGifticonTokenCipher to decode and validate the
configured key during construction or initialization, then cache and reuse the
validated SecretKey in secretKey() and the encrypt/decrypt paths. Preserve the
existing optional-empty-key behavior if supported, while ensuring invalid
non-empty keys fail during application startup rather than during token
operations.
In
`@manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql`:
- Around line 7-18: 운영 테이블에 대한 검증 및 인덱스 생성을 온라인 단계로 분리하세요. V22의
fk_cancellation_request_meeting_match와 chk_cancellation_request_target은 NOT
VALID로 추가한 뒤 별도 VALIDATE 단계에서 검증하고, 같은 파일의 인덱스는 CONCURRENTLY 사용을 확인하세요. V24의
UNIQUE 인덱스는 중복 데이터를 사전 정리한 후 동시 생성하고, V25 인덱스는 테이블 규모에 맞춰 동시 생성을 적용하세요. V26의 두
FK는 NOT VALID 추가 후 별도 검증으로 분리하며, 해당 인덱스에는 동시 생성을 검토하세요. 영향을 받는 파일은
manabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sql(7-18,
20-25),
manabom/src/main/resources/db/migration/V24__add_unique_active_chat_member.sql(1-2),
manabom/src/main/resources/db/migration/V25__add_meeting_verification_failure_notification.sql(4-5),
manabom/src/main/resources/db/migration/V26__add_structured_system_message_fields.sql(8-10,
12-14, 20-22)입니다.
In
`@manabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sql`:
- Around line 4-10: Update
manabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sql
lines 4-10 to add the foreign key as NOT VALID and validate it separately, and
use concurrent index creation where supported. Update
manabom/src/main/resources/db/migration/V31__add_gifticon_template_token.sql
lines 4-6 to create the UNIQUE index online, or remove its creation if V32
immediately drops it. Update
manabom/src/main/resources/db/migration/V34__hold_paid_ting_for_gifticon.sql
lines 5-17 to add CHECK constraints as NOT VALID and validate them separately.
In
`@manabom/src/main/resources/db/migration/V36__remove_gifticon_order_template_token.sql`:
- Around line 1-2: Review the V36 migration’s DROP COLUMN operation against the
deployment strategy: for rolling deployments, first deploy an application
version that no longer reads encrypted_template_token, then remove the column in
a subsequent migration; otherwise explicitly confirm this migration runs only
during a coordinated downtime deployment with no old GifticonOrder instances.
In
`@manabom/src/main/resources/db/migration/V37__add_gifticon_order_sender_nickname.sql`:
- Around line 16-17: Update migration V37’s gifticon_order sender_nickname
constraint change to use an online, staged validation approach that avoids a
long strong lock on the large table. Verify existing NULL data first, add and
validate an equivalent constraint in a low-impact step, then enforce NOT NULL
only through the safest supported operation for this database; otherwise
schedule the direct ALTER during a maintenance window.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b23044d6-9614-4c18-856b-97dca7dd0e81
📒 Files selected for processing (87)
manabom/src/main/java/mannabom_server/manabom/application/admin/dto/request/AdminConfigureGifticonTokenRequest.javamanabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminGifticonProductResponse.javamanabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminGifticonProductSliceResponse.javamanabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminGifticonSyncResponse.javamanabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminGifticonService.javamanabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminReportService.javamanabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminWalletService.javamanabom/src/main/java/mannabom_server/manabom/application/currency/service/TingTransactionRecorder.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/dto/response/GifticonProductResponse.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/dto/response/GifticonProductSliceResponse.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEvent.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/port/GifticonOrderRequester.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/port/GifticonTemplateProvider.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/port/GifticonTokenCipher.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/port/command/GifticonOrderCommand.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/scheduler/GifticonCatalogSyncScheduler.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/scheduler/GifticonOrderRetryScheduler.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonCatalogService.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonCatalogSynchronizer.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessor.javamanabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderService.javamanabom/src/main/java/mannabom_server/manabom/application/like/service/LikeService.javamanabom/src/main/java/mannabom_server/manabom/application/messageRequest/dto/request/SendMessageRequestDto.javamanabom/src/main/java/mannabom_server/manabom/application/messageRequest/service/MessageRequestService.javamanabom/src/main/java/mannabom_server/manabom/application/partner/service/PartnerService.javamanabom/src/main/java/mannabom_server/manabom/application/signal/dto/response/RespondSignalResponseDto.javamanabom/src/main/java/mannabom_server/manabom/application/signup/service/SignupService.javamanabom/src/main/java/mannabom_server/manabom/domain/admin/enums/AdminAuditActionType.javamanabom/src/main/java/mannabom_server/manabom/domain/admin/enums/AdminAuditTargetType.javamanabom/src/main/java/mannabom_server/manabom/domain/currency/entity/TingTransaction.javamanabom/src/main/java/mannabom_server/manabom/domain/currency/enums/TingBalanceType.javamanabom/src/main/java/mannabom_server/manabom/domain/currency/enums/TingTransactionReferenceType.javamanabom/src/main/java/mannabom_server/manabom/domain/currency/enums/TingTransactionType.javamanabom/src/main/java/mannabom_server/manabom/domain/currency/repository/TingTransactionRepository.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/entity/GifticonOrder.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/entity/GifticonProduct.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/enums/GifticonOrderStatus.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/enums/GifticonPaymentStatus.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/repository/GifticonOrderRepository.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/repository/GifticonProductRepository.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/service/GifticonPriceCalculator.javamanabom/src/main/java/mannabom_server/manabom/domain/gifticon/vo/GifticonTemplateSnapshot.javamanabom/src/main/java/mannabom_server/manabom/domain/messageRequest/entity/MessageRequest.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/config/GifticonPricingConfiguration.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/KakaoGiftbizClient.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/KakaoGiftbizOrderClient.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/KakaoGiftbizTemplateMapper.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/config/GiftbizProperties.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/dto/KakaoGiftbizOrderRequest.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/dto/KakaoGiftbizTemplatePage.javamanabom/src/main/java/mannabom_server/manabom/infrastructure/security/crypto/AesGcmGifticonTokenCipher.javamanabom/src/main/java/mannabom_server/manabom/policy/config/ConfigRegister.javamanabom/src/main/java/mannabom_server/manabom/policy/config/GifticonPricingProperties.javamanabom/src/main/java/mannabom_server/manabom/presentation/admin/controller/AdminGifticonController.javamanabom/src/main/java/mannabom_server/manabom/presentation/gifticon/controller/GifticonController.javamanabom/src/main/java/mannabom_server/manabom/presentation/messageRequest/controller/MessageRequestController.javamanabom/src/main/resources/application.ymlmanabom/src/main/resources/db/migration/V21__add_meeting_cancellation_tables.sqlmanabom/src/main/resources/db/migration/V22__move_meeting_cancellation_to_match.sqlmanabom/src/main/resources/db/migration/V23__remove_withdrawn_cancellation_status.sqlmanabom/src/main/resources/db/migration/V24__add_unique_active_chat_member.sqlmanabom/src/main/resources/db/migration/V25__add_meeting_verification_failure_notification.sqlmanabom/src/main/resources/db/migration/V26__add_structured_system_message_fields.sqlmanabom/src/main/resources/db/migration/V27__persist_meeting_verification_result.sqlmanabom/src/main/resources/db/migration/V28__create_gifticon_product.sqlmanabom/src/main/resources/db/migration/V29__allow_null_gifticon_sales_period.sqlmanabom/src/main/resources/db/migration/V30__link_gifticon_to_message_request.sqlmanabom/src/main/resources/db/migration/V31__add_gifticon_template_token.sqlmanabom/src/main/resources/db/migration/V32__encrypt_gifticon_template_token.sqlmanabom/src/main/resources/db/migration/V33__create_gifticon_order.sqlmanabom/src/main/resources/db/migration/V34__hold_paid_ting_for_gifticon.sqlmanabom/src/main/resources/db/migration/V35__create_ting_transaction.sqlmanabom/src/main/resources/db/migration/V36__remove_gifticon_order_template_token.sqlmanabom/src/main/resources/db/migration/V37__add_gifticon_order_sender_nickname.sqlmanabom/src/main/resources/static/admin/app.jsmanabom/src/main/resources/static/admin/index.htmlmanabom/src/main/resources/static/admin/styles.cssmanabom/src/test/java/mannabom_server/manabom/application/gifticon/service/GifticonCatalogServiceTest.javamanabom/src/test/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessorTest.javamanabom/src/test/java/mannabom_server/manabom/domain/gifticon/service/GifticonPriceCalculatorTest.javamanabom/src/test/java/mannabom_server/manabom/domain/messageRequest/entity/MessageRequestGiftPaymentTest.javamanabom/src/test/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/KakaoGiftbizOrderClientTest.javamanabom/src/test/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/KakaoGiftbizTemplateMapperTest.javamanabom/src/test/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/dto/KakaoGiftbizOrderRequestTest.javamanabom/src/test/java/mannabom_server/manabom/infrastructure/external/kakao/giftbiz/dto/KakaoGiftbizTemplatePageTest.javamanabom/src/test/java/mannabom_server/manabom/infrastructure/security/crypto/AesGcmGifticonTokenCipherTest.java
| @Size(max = 500, message = "reason은 500자를 초과할 수 없습니다.") | ||
| private String reason; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
reason 필수 검증 누락.
AdminGifticonService.configureTemplateToken은 request.getReason()을 그대로 감사 로그에 기록하는데, 이 필드에는 @NotBlank가 없어 null/공백으로 저장될 수 있습니다. 프론트엔드(app.js saveGifticonToken)는 토큰과 사유를 모두 필수로 검증하지만, API를 직접 호출하면 이 제약을 우회할 수 있어 변경 이력 관리 목적(PR 목표)이 약화됩니다.
🛡️ 제안 diff
+ `@NotBlank`(message = "reason은 필수입니다.")
`@Size`(max = 500, message = "reason은 500자를 초과할 수 없습니다.")
private String reason;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Size(max = 500, message = "reason은 500자를 초과할 수 없습니다.") | |
| private String reason; | |
| `@NotBlank`(message = "reason은 필수입니다.") | |
| `@Size`(max = 500, message = "reason은 500자를 초과할 수 없습니다.") | |
| private String reason; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/java/mannabom_server/manabom/application/admin/dto/request/AdminConfigureGifticonTokenRequest.java`
around lines 16 - 17, Update the reason field in
AdminConfigureGifticonTokenRequest to require a non-null, non-blank value while
retaining the existing 500-character maximum validation and message. Ensure
configureTemplateToken receives only valid audit-log reasons through request
validation.
| private TingTransaction record( | ||
| TingWallet wallet, | ||
| TingBalanceType balanceType, | ||
| TingTransactionType transactionType, | ||
| int amountDelta, | ||
| int balanceAfter, | ||
| TingTransactionReferenceType referenceType, | ||
| String referenceId, | ||
| String idempotencyKey, | ||
| String description | ||
| ) { | ||
| if (wallet == null) { | ||
| throw new IllegalArgumentException("팅 거래를 기록할 지갑은 필수입니다."); | ||
| } | ||
| return tingTransactionRepository.save(new TingTransaction( | ||
| wallet.getUserId(), | ||
| balanceType, | ||
| transactionType, | ||
| amountDelta, | ||
| balanceAfter, | ||
| referenceType, | ||
| referenceId, | ||
| idempotencyKey, | ||
| description | ||
| )); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
fd -i 'TingTransactionRecorder|TingTransaction|TingTransactionRepository' .
echo "== inspect recorder =="
RECORDER="$(fd -i 'TingTransactionRecorder.java' . | head -n1)"
if [ -n "${RECORDER:-}" ]; then
nl -ba "$RECORDER" | sed -n '1,180p'
fi
echo "== inspect entity/repository candidates =="
for f in $(fd -i 'TingTransaction.*java|.*Repository.java' . | rg ' TingTransaction |Repository' | head -n 20); do
echo "--- $f"
nl -ba "$f" | sed -n '1,220p'
done
echo "== idempotencyKey usages =="
rg -n "idempotencyKey|idempotency" . -g '*.java' | sed -n '1,220p'Repository: mannabom/mannabomServer
Length of output: 747
🏁 Script executed:
#!/bin/bash
set -euo pipefail
RECORDER="$(fd -i 'TingTransactionRecorder.java' . | head -n1)"
ENTITY="$(fd -i 'TingTransaction.java' . | head -n1)"
REPO="$(fd -i 'TingTransactionRepository.java' . | head -n1)"
echo "== recorder lines =="
awk '{printf "%5d %s\n", NR, $0}' "$RECORDER"
echo "== entity lines =="
awk '{printf "%5d %s\n", NR, $0}' "$ENTITY"
echo "== repository lines =="
awk '{printf "%5d %s\n", NR, $0}' "$REPO"
echo "== idempotencyKey usages =="
rg -n "idempotencyKey|idempotency" . -g '*.java' | sed -n '1,220p'
echo "== unique constraint related text =="
rg -n "uniqueConstraint|UniqueConstraint|unique|uniqueConstraints|idempotencyKey" . -g '*.java' -g '*.sql' -g '*.xml' -g '*.yml' -g '*.yaml' | sed -n '1,260p'Repository: mannabom/mannabomServer
Length of output: 19981
TingTransactionRecorder.record()에 실제 중복 검증을 추가하세요.
current record()는 idempotencyKey를 TingTransaction에 저장할 뿐이며, TingTransactionRepository에도 해당 키 조회 메서드가 없고 TingTransaction에는 uniqueConstraints가 없어 저장 단계에서 중복을 막을 수 없습니다. DB unique 제약만 추가하면 재시도 예외로 전체 트랜잭션이 실패할 수 있으므로, 저장 전 동일 키 조회 후 기존 레코드를 반환하는 멱등성 흐름이 필요합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/java/mannabom_server/manabom/application/currency/service/TingTransactionRecorder.java`
around lines 62 - 87, Update TingTransactionRecorder.record() to check for an
existing transaction by idempotencyKey before saving; add the required
TingTransactionRepository lookup and return the existing record when found,
otherwise save a new transaction. Ensure the persistence model enforces
uniqueness for the idempotency key while preserving the pre-save lookup flow.
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| public void requestGift(GifticonOrderReadyEvent event) { | ||
| gifticonOrderProcessor.process(event.gifticonOrderId()); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg 'GifticonOrderReadyEventListener|GifticonOrderProcessor|GifticonOrder' | sed -n '1,120p'
echo
echo "EventListener outline:"
ast-grep outline manabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.java || true
echo
echo "EventListener.java:"
cat -n manabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.java
echo
echo "Processor outline:"
ast-grep outline manabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessor.java || true
echo
echo "Processor.java head/tail:"
wc -l manabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessor.java
sed -n '1,140p' manabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessor.java | cat -n
echo
echo "Search task executor annotations/Async/TransactionEventListener config:"
rg -n "`@EnableAsync`|ThreadPoolTaskExecutor|`@Async`|TransactionEventListener|Propagation\.REQUIRES_NEW|`@TransactionalEventListener`|async|executor" manabom/src/main/java || trueRepository: mannabom/mannabomServer
Length of output: 8752
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "AsyncConfig.java:"
cat -n manabom/src/main/java/mannabom_server/manabom/infrastructure/config/AsyncConfig.java
echo
echo "Event publishing sites:"
rg -n "ApplicationEventPublisher|publishEvent|new GifticonOrderReadyEvent|GifticonOrderReadyEvent" manabom/src/main/java | sed -n '1,160p'
echo
echo "GifticonOrderRepository relevant methods:"
cat -n manabom/src/main/java/mannabom_server/manabom/domain/gifticon/repository/GifticonOrderRepository.java
echo
echo "MatchingEventListener comparable async transaction listener:"
cat -n manabom/src/main/java/mannabom_server/manabom/application/meeting/handler/MatchingEventListener.java
echo
echo "notification listener comparable async transaction listener:"
cat -n manabom/src/main/java/mannabom_server/manabom/application/notification/handler/NotificationEventListener.javaRepository: mannabom/mannabomServer
Length of output: 11275
외부 발송을 비동기 및 짧은 트랜잭션으로 분리하세요. 이벤트 리스너가 없으면 동기 실행되고, 처리기는 REQUIRES_NEW 트랜잭션에서 PESSIMISTIC_WRITE 잠금을 유지한 채 원격 호출을 수행합니다.
manabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.java#L15-L17:@Async또는 내구성 있는 작업 큐에 주문 ID를 위임하세요.manabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessor.java#L30-L64: 잠금을 유지한 채 완료된 요청 처리를 마친 뒤 요청만 하고, 성공 시 별도 트랜잭션에서markRequested()를 기록하세요.
📍 Affects 2 files
manabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.java#L15-L17(this comment)manabom/src/main/java/mannabom_server/manabom/application/gifticon/service/GifticonOrderProcessor.java#L30-L64
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/java/mannabom_server/manabom/application/gifticon/event/GifticonOrderReadyEventListener.java`
around lines 15 - 17, 외부 발송이 동기 처리되거나 잠금 트랜잭션을 장시간 점유하지 않도록 분리하세요.
GifticonOrderReadyEventListener.java의 requestGift 메서드는 `@Async` 또는 내구성 있는 작업 큐로 주문
ID만 위임하세요. GifticonOrderProcessor.java의 처리 흐름은 PESSIMISTIC_WRITE 잠금 하에서 완료된 요청
처리를 마친 뒤 원격 요청만 수행하고, 성공한 경우 별도 트랜잭션에서 markRequested()를 기록하도록 변경하세요.
| import mannabom_server.manabom.application.currency.service.TingTransactionRecorder; | ||
| import mannabom_server.manabom.domain.auth.entity.RefreshToken; | ||
| import mannabom_server.manabom.domain.auth.repository.RefreshTokenRepository; | ||
| import mannabom_server.manabom.domain.currency.entity.TingWallet; | ||
| import mannabom_server.manabom.domain.currency.enums.TingTransactionReferenceType; | ||
| import mannabom_server.manabom.domain.currency.enums.TingTransactionType; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
보너스가 0인 경우에도 0원 원장 행이 기록됩니다.
signupBonusEventTing이 0이어도 recordEvent가 항상 호출되어 amountDelta=0인 TingTransaction 행이 생성됩니다. 다른 지출 경로(LikeService, PartnerService)는 비용이 0보다 큰 경우에만 기록하는 패턴을 따르는데, 여기서는 그 조건이 빠져 있어 불필요한 원장 잡음이 누적됩니다.
🔧 0보다 큰 경우에만 기록하도록 가드 추가
if(!tingWalletRepository.existsById(user.getUserId())) {
TingWallet wallet = new TingWallet(user.getUserId());
//wallet.addEventTing(initialPoints); (기본 지급을 답변 보상으로 대체)
- wallet.addEventTing(signupBonusEventTing);
- tingWalletRepository.save(wallet);
- tingTransactionRecorder.recordEvent(
- wallet,
- TingTransactionType.SIGNUP_BONUS,
- signupBonusEventTing,
- TingTransactionReferenceType.USER,
- String.valueOf(user.getUserId()),
- "SIGNUP:" + user.getUserId() + ":BONUS",
- "회원가입 프로필 작성 보너스"
- );
+ if (signupBonusEventTing > 0) {
+ wallet.addEventTing(signupBonusEventTing);
+ }
+ tingWalletRepository.save(wallet);
+ if (signupBonusEventTing > 0) {
+ tingTransactionRecorder.recordEvent(
+ wallet,
+ TingTransactionType.SIGNUP_BONUS,
+ signupBonusEventTing,
+ TingTransactionReferenceType.USER,
+ String.valueOf(user.getUserId()),
+ "SIGNUP:" + user.getUserId() + ":BONUS",
+ "회원가입 프로필 작성 보너스"
+ );
+ }Also applies to: 70-70, 417-431
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/java/mannabom_server/manabom/application/signup/service/SignupService.java`
around lines 8 - 13, Update the signup bonus recording flow in SignupService so
TingTransactionRecorder.recordEvent is invoked only when signupBonusEventTing is
greater than zero. Preserve the existing transaction details and behavior for
positive bonuses, while skipping creation of zero-amount ledger rows.
| public boolean isOrderableAt(LocalDateTime now) { | ||
| return hasTemplateToken() && isAvailableAt(now); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
0팅 상품을 주문 가능 상태로 노출하지 마세요.
tingPrice == 0인 상품도 현재는 토큰·기간만 충족하면 주문 가능으로 판정되지만, 메시지 요청 생성은 이를 거절합니다. 주문 가능 여부에 양수 가격을 포함해 선택 이후 실패를 막아야 합니다.
수정 제안
public boolean isOrderableAt(LocalDateTime now) {
- return hasTemplateToken() && isAvailableAt(now);
+ return tingPrice > 0 && hasTemplateToken() && isAvailableAt(now);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public boolean isOrderableAt(LocalDateTime now) { | |
| return hasTemplateToken() && isAvailableAt(now); | |
| } | |
| public boolean isOrderableAt(LocalDateTime now) { | |
| return tingPrice > 0 && hasTemplateToken() && isAvailableAt(now); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/java/mannabom_server/manabom/domain/gifticon/entity/GifticonProduct.java`
around lines 163 - 165, Update GifticonProduct.isOrderableAt to require a
strictly positive tingPrice in addition to hasTemplateToken() and
isAvailableAt(now), so zero-priced products are not reported as orderable.
| giftbiz: | ||
| base-url: ${KAKAO_GIFTBIZ_BASE_URL:https://gateway-giftbiz.kakao.com} | ||
| authorization: ${KAKAO_GIFTBIZ_AUTHORIZATION:} | ||
| token-encryption-key: ${GIFTICON_TOKEN_ENCRYPTION_KEY:} | ||
| sender-name: ${KAKAO_GIFTBIZ_SENDER_NAME:만나봄} | ||
| text: ${KAKAO_GIFTBIZ_TEXT:마음이 도착했어요} | ||
| request-timeout-seconds: ${KAKAO_GIFTBIZ_REQUEST_TIMEOUT_SECONDS:10} | ||
| sync: | ||
| enabled: ${KAKAO_GIFTBIZ_SYNC_ENABLED:true} | ||
| initial-delay: ${KAKAO_GIFTBIZ_SYNC_INITIAL_DELAY:10000} | ||
| fixed-delay: ${KAKAO_GIFTBIZ_SYNC_FIXED_DELAY:86400000} | ||
| max-pages: ${KAKAO_GIFTBIZ_SYNC_MAX_PAGES:100} |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
인증 정보가 없을 때 동기화를 기본 비활성화하세요.
authorization의 기본값은 비어 있지만 동기화는 기본 활성화되어 있어, 설정되지 않은 환경에서는 스케줄 작업이 항상 실패합니다. 기본값을 false로 바꾸거나 인증 정보가 없으면 시작 시 즉시 설정 오류로 처리하세요.
수정 예시
- enabled: ${KAKAO_GIFTBIZ_SYNC_ENABLED:true}
+ enabled: ${KAKAO_GIFTBIZ_SYNC_ENABLED:false}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| giftbiz: | |
| base-url: ${KAKAO_GIFTBIZ_BASE_URL:https://gateway-giftbiz.kakao.com} | |
| authorization: ${KAKAO_GIFTBIZ_AUTHORIZATION:} | |
| token-encryption-key: ${GIFTICON_TOKEN_ENCRYPTION_KEY:} | |
| sender-name: ${KAKAO_GIFTBIZ_SENDER_NAME:만나봄} | |
| text: ${KAKAO_GIFTBIZ_TEXT:마음이 도착했어요} | |
| request-timeout-seconds: ${KAKAO_GIFTBIZ_REQUEST_TIMEOUT_SECONDS:10} | |
| sync: | |
| enabled: ${KAKAO_GIFTBIZ_SYNC_ENABLED:true} | |
| initial-delay: ${KAKAO_GIFTBIZ_SYNC_INITIAL_DELAY:10000} | |
| fixed-delay: ${KAKAO_GIFTBIZ_SYNC_FIXED_DELAY:86400000} | |
| max-pages: ${KAKAO_GIFTBIZ_SYNC_MAX_PAGES:100} | |
| giftbiz: | |
| base-url: ${KAKAO_GIFTBIZ_BASE_URL:https://gateway-giftbiz.kakao.com} | |
| authorization: ${KAKAO_GIFTBIZ_AUTHORIZATION:} | |
| token-encryption-key: ${GIFTICON_TOKEN_ENCRYPTION_KEY:} | |
| sender-name: ${KAKAO_GIFTBIZ_SENDER_NAME:만나봄} | |
| text: ${KAKAO_GIFTBIZ_TEXT:마음이 도착했어요} | |
| request-timeout-seconds: ${KAKAO_GIFTBIZ_REQUEST_TIMEOUT_SECONDS:10} | |
| sync: | |
| enabled: ${KAKAO_GIFTBIZ_SYNC_ENABLED:false} | |
| initial-delay: ${KAKAO_GIFTBIZ_SYNC_INITIAL_DELAY:10000} | |
| fixed-delay: ${KAKAO_GIFTBIZ_SYNC_FIXED_DELAY:86400000} | |
| max-pages: ${KAKAO_GIFTBIZ_SYNC_MAX_PAGES:100} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@manabom/src/main/resources/application.yml` around lines 94 - 105, Update the
giftbiz.sync.enabled configuration default to false so synchronization is
disabled when KAKAO_GIFTBIZ_AUTHORIZATION is unset, while preserving explicit
environment values that enable synchronization.
| CREATE UNIQUE INDEX uk_chat_members_active_room_user | ||
| ON chat_members (room_id, user_id) | ||
| WHERE status = 'ACTIVATE'; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
활성 상태값을 ACTIVE로 맞추세요.
선행 manabom/src/main/resources/db/migration/V14__add_column.sql:1-6에서는 상태 기본값을 ACTIVE로 정의했지만, 이 인덱스는 ACTIVATE를 필터링합니다. 따라서 실제 활성 멤버에는 UNIQUE 제약이 적용되지 않아 중복 멤버가 허용됩니다. 이미 중복 데이터가 있다면 정리 후 올바른 인덱스를 생성해야 합니다.
🔧 수정안
CREATE UNIQUE INDEX uk_chat_members_active_room_user
ON chat_members (room_id, user_id)
- WHERE status = 'ACTIVATE';
+ WHERE status = 'ACTIVE';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CREATE UNIQUE INDEX uk_chat_members_active_room_user | |
| ON chat_members (room_id, user_id) | |
| WHERE status = 'ACTIVATE'; | |
| CREATE UNIQUE INDEX uk_chat_members_active_room_user | |
| ON chat_members (room_id, user_id) | |
| WHERE status = 'ACTIVE'; |
🧰 Tools
🪛 Squawk (2.59.0)
[warning] 1-3: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.
(require-concurrent-index-creation)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/resources/db/migration/V24__add_unique_active_chat_member.sql`
around lines 1 - 3, Update the partial unique index
uk_chat_members_active_room_user to filter chat_members rows with status ACTIVE,
matching the existing status default. Before creating the corrected index,
remove any existing duplicate active room/user records if the migration must
handle pre-existing duplicates.
| product_price INTEGER NOT NULL CHECK (product_price >= 0), | ||
| ting_price INTEGER NOT NULL CHECK (ting_price >= 0), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
ting_price의 허용 범위를 V34 결제 제약조건과 일치시키세요.
현재는 ting_price = 0 상품을 저장할 수 있지만, V34__hold_paid_ting_for_gifticon.sql은 기프티콘이 연결된 메시지 요청의 held_gift_ting > 0을 요구합니다. 따라서 0 팅 상품은 카탈로그에는 저장된 뒤 메시지 요청 생성 시 DB 제약조건으로 실패합니다. 무료 상품을 지원하지 않는다면 ting_price > 0으로 변경하고, 지원한다면 V34와 결제 초기화 로직을 함께 수정해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@manabom/src/main/resources/db/migration/V28__create_gifticon_product.sql`
around lines 19 - 20, Update the ting_price constraint in the gifticon product
schema to require values greater than zero, aligning catalog validation with
V34__hold_paid_ting_for_gifticon.sql and the existing payment initialization
flow. Preserve the nonnegative product_price constraint unchanged.
| ALTER TABLE gifticon_product | ||
| RENAME COLUMN template_token TO encrypted_template_token; | ||
|
|
||
| ALTER TABLE gifticon_product | ||
| ALTER COLUMN encrypted_template_token TYPE VARCHAR(1024); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate migration files =="
fd -a 'V3[12]__.*\.sql$' . | sed 's#^\./##' || true
echo
echo "== relevant file contents =="
for f in $(fd 'V3[12]__.*\.sql$' .); do
echo "--- $f ($(wc -l < "$f") lines) ---"
cat -n "$f"
done
echo
echo "== search template_token usages =="
rg -n "template_token|encrypted_template_token|gifticon_product" -S . \
-g '!**/.git/**' -g '!**/node_modules/**' -g '!**/build/**' -g '!**/dist/**' || trueRepository: mannabom/mannabomServer
Length of output: 6977
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== inspect existing template token access path =="
sed -n '130,240p' manabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminGifticonService.java | cat -n
echo
echo "== inspect entity =="
cat -n manabom/src/main/java/mannabom_server/manabom/domain/gifticon/entity/GifticonProduct.java
echo
echo "== deterministic migration effect probe =="
python3 - <<'PY'
rows = [
{"template_token": "plain-token-abc"},
{"template_token": None},
]
# V32 behavior: rename column and widen type, no UPDATE/SET
for row in rows:
print(row)
PYRepository: mannabom/mannabomServer
Length of output: 7405
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== all GifticonProduct template token references =="
rg -n "encryptedTemplateToken|configureEncryptedTemplateToken|hasTemplateToken|isOrderableAt|template_token" manabom/src/main/java -S || true
echo
echo "== service snippets around GifticonProduct =="
rg -n -C 8 "GifticonProduct|gifticonProduct|encryptedTemplateToken" manabom/src/main/java/mannabom_server/manabom/application/admin -S || true
echo
echo "== DTO/controller snippets =="
rg -n -C 10 "template_token|Gifticon|OrderTemplate|Giftbiz" manabom/src/main/java/mannabom_server/manabom/domain manabom/src/main/java/mannabom_server/manabom/application -S || trueRepository: mannabom/mannabomServer
Length of output: 50380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== inspect decrypt/encrypt cipher references and token fetch =="
rg -n -C 6 "decrypt|configureEncryptedTemplateToken|encryptedTemplateToken|hasTemplateToken|template_token|Giftbiz|GifticonOrderProcessor|AdminGifticonService::configureTemplateToken" manabom/src/main/java \
-g '!**/admin/**' || true
rg -n -C 6 "decrypt|configureEncryptedTemplateToken|template_token|Giftbiz|GifticonOrderProcessor|AdminGifticonService::configureTemplateToken" manabom/src/main/java \
-g '**/gifticon/**' -g '**/external/**' || true
echo
echo "== files with decrypt/configure enc =="
fd -e java . manabom/src/main/java | xargs rg -l "decrypt|EncryptedToken|Encrypted token|encrypt\\(request|configureEncryptedTemplateToken" 2>/dev/null || trueRepository: mannabom/mannabomServer
Length of output: 50380
기존 template_token 값을 암호화 컬럼에 넘기지 마세요.
V31이 적용된 환경에서는 평문 토큰이 template_token에 남아 있고, 이 migration은 단순히 이름을 encrypted_template_token으로 바꿉니다. 기존 token이 있더라도 admin에서 재등록 후 암호화 backfill이라도 해야만 Kakao 구매 API에 평문이 전송되지 않습니다. 현재 migration만으로는 기존 토큰이 평문으로 남아 보안 위험이 큽니다.
🧰 Tools
🪛 Squawk (2.59.0)
[warning] 5-5: Renaming a column may break existing clients.
(renaming-column)
[warning] 8-8: Changing a column type requires an ACCESS EXCLUSIVE lock on the table which blocks reads and writes while the table is rewritten. Changing the type of the column may also break other clients reading from the table.
(changing-column-type)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@manabom/src/main/resources/db/migration/V32__encrypt_gifticon_template_token.sql`
around lines 4 - 8, Update the V32 migration so existing plaintext values from
template_token are never retained in encrypted_template_token: do not use a
direct rename that carries the data, or clear the renamed column immediately
before any application use. Preserve the encrypted_template_token column
definition while requiring existing products to be re-registered or separately
backfilled through the encryption path.
| ADD CONSTRAINT ck_message_request_gift_payment_status | ||
| CHECK ( | ||
| (gifticon_product_id IS NULL | ||
| AND held_gift_ting = 0 | ||
| AND gift_payment_status IS NULL) | ||
| OR | ||
| (gifticon_product_id IS NOT NULL | ||
| AND held_gift_ting > 0 | ||
| AND gift_payment_status IN ('HELD', 'CAPTURED', 'RELEASED')) | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
gift_payment_status = NULL 상태가 CHECK를 우회할 수 있습니다.
PostgreSQL의 CHECK는 결과가 FALSE일 때만 거부하고 NULL은 통과시킵니다. 따라서 gifticon_product_id IS NOT NULL, held_gift_ting > 0, gift_payment_status IS NULL이면 IN (...) 결과가 NULL이 되어 전체 CHECK가 통과할 수 있습니다. 해당 분기에 gift_payment_status IS NOT NULL을 명시하세요.
🧰 Tools
🪛 Squawk (2.59.0)
[warning] 8-17: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@manabom/src/main/resources/db/migration/V34__hold_paid_ting_for_gifticon.sql`
around lines 8 - 17, Update the ck_message_request_gift_payment_status CHECK
constraint so the gifticon_product_id IS NOT NULL branch explicitly requires
gift_payment_status IS NOT NULL before validating its allowed values, preventing
NULL statuses from bypassing the constraint.
작업 내용
기프티콘 상품 관리
lastSyncedAt기준으로 비활성화기프티콘 템플릿 토큰 관리
메시지 요청 기프티콘
gifticonProductId연결HELD처리CAPTURED처리 후 카카오 Gift Biz 주문 생성RELEASED처리 후 보류했던 유상팅 복구만나봄 - {프로필 닉네임}형식으로 전달externalOrderId와externalKey를 메시지 요청 기준으로 생성팅 거래 원장
ting_transaction테이블 추가Summary by CodeRabbit