Skip to content

Conversation

@kgy1008
Copy link
Member

@kgy1008 kgy1008 commented Oct 25, 2025

📣 Jira Ticket

EDMT-458

👩‍💻 작업 내용

주요 변경사항

  • PointHistory 엔티티 및 테이블 추가: 모든 포인트 변동 이력 추적 가능
  • 트랜잭션 타입별 관리: DEDUCT(차감), COMPENSATION(보상), CHARGE(충전), REFUND(환불), ADMIN_ADJUST(관리자 조정)
  • AI 작업 추적: taskId를 통해 어떤 AI 작업으로 포인트가 차감/복구되었는지 추적
  • 분산 환경 지원: 보상 트랜잭션(COMPENSATION)으로 실패 시 포인트 복구 이력 관리
  • 성능 최적화: member_id, task_id, created_at 인덱스 추가

구현 세부사항

  • PointHistory 엔티티: Member와 ManyToOne 관계, 불변 데이터로 설계
  • PointTransactionType enum: 5가지 트랜잭션 타입 정의
  • PointService 개선:
    • deductPoints(): taskId와 함께 차감 히스토리 기록
    • compensatePoints(): 실패 시 보상 히스토리 기록
  • Flyway 마이그레이션: V11__Add_point_history_table.sql

패키지 구조 개선

  • PointService를 member 패키지에서 point 패키지로 이동
  • PointException 및 PointErrorCode 추가로 예외 처리 체계 확립

📝 리뷰 요청 & 논의하고 싶은 내용

  1. PointHistory 테이블에 balanceBefore/balanceAfter 컬럼은 제외했습니다. 필요 시 추가 가능합니다.
  2. taskId는 nullable로 설계하여 CHARGE 등 AI 작업과 무관한 트랜잭션도 지원합니다.
  3. 향후 결제 충전 기능 확장 시 CHARGE 타입을 활용할 수 있습니다.

Test Plan

✅ Manual Testing Checklist

  • 애플리케이션 재시작 시 Flyway 마이그레이션 정상 실행 확인
  • point_history 테이블 생성 및 인덱스 확인
  • AI 생성 요청 시 포인트 차감과 함께 히스토리 DEDUCT 타입으로 기록 확인
  • taskId가 올바르게 연결되는지 확인
  • Member 조회 시 point_history와 연관관계 정상 동작 확인

🧪 Automated Tests

  • Unit tests pass: ./gradlew test
  • Build succeeds: ./gradlew build

🔍 Code Quality

  • PointService 패키지 구조 개선
  • 예외 처리 체계 확립 (PointException, PointErrorCode)
  • 불변 엔티티 설계 (PointHistory)

Deployment Notes

  • Database migrations included: V11__Add_point_history_table.sql
  • 인덱스 추가로 쿼리 성능 최적화
  • 기존 포인트 데이터에 대한 히스토리는 없음 (신규 트랜잭션부터 기록)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새로운 기능

    • 포인트 거래 이력(포인트 히스토리) 저장 기능 추가
    • 포인트 보상·보정 처리 기능 추가
    • AI 작업 상태(PENDING, IN_PROGRESS, COMPLETED, FAILED) 관리 추가
  • 개선사항

    • 포인트 차감/보상 흐름 정비로 거래 신뢰성 향상(중복 방지 인덱스 포함)
    • 관련 오류 코드 및 예외 체계 정리로 에러 처리 명확화
  • 주의

    • 포인트 관련 공개 API 및 오류 코드 변경으로 일부 통합 지점에 영향이 있을 수 있음

@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2025

Walkthrough

포인트 기능을 com.edukit.core.member에서 com.edukit.core.point로 분리하고, 포인트 이력 테이블 및 관련 엔티티/레포지토리/예외를 추가했으며 PointService API를 변경(세 번째 taskId 인자 추가, deductPoints 반환형이 void로 변경)하고 AI 태스크 생성 이후에 포인트를 차감하도록 흐름을 변경했습니다.

Changes

코호트 / 파일(들) 변경 요약
API 사용처 변경
edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java
PointService 임포트 경로 변경(com.edukit.core.point.service.PointService); 멤버를 먼저 조회하고 AI 태스크 생성 후 deductPoints(memberId, points, taskId) 호출로 차감 시점 및 시그니처 변경
DB 마이그레이션 (포인트 이력 테이블)
edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql
point_history 테이블 추가(거래타입 enum, amount, task_id, created_at/modified_at), FK 및 인덱스(UQ_point_history_task_transaction, IDX_point_history_*) 추가
포인트 도메인: 엔티티/리포지토리/열거형
edukit-core/src/main/java/com/edukit/core/point/db/entity/PointHistory.java, .../db/repository/PointHistoryRepository.java, .../db/enums/PointTransactionType.java
PointHistory JPA 엔티티 및 JpaRepository 추가; 거래 타입 열거형(CHARGE, DEDUCT, REFUND, COMPENSATION, ADMIN_ADJUST) 추가
포인트 오류/예외 추가
edukit-core/src/main/java/com/edukit/core/point/exception/PointErrorCode.java, .../PointException.java
포인트 관련 에러코드(INSUFFICIENT_POINTS) 및 PointException 추가
포인트 서비스 리팩토링/이동
edukit-core/src/main/java/com/edukit/core/point/service/PointService.java
새 PointService 추가: void deductPoints(Long memberId, int pointsToDeduct, Long taskId)Member compensatePoints(Long memberId, int pointsToCompensate, Long taskId) 구현, 포인트 차감/보상 시 PointHistory 생성 및 저장
기존 포인트 서비스 제거
edukit-core/src/main/java/com/edukit/core/member/service/PointService.java
기존 패키지의 PointService 클래스 삭제
회원 오류 코드 정리
edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java
enum 상수 일부 제거(예: INSUFFICIENT_POINTS 등 삭제)
AI 태스크 상태 열거형 추가
edukit-core/src/main/java/com/edukit/core/studentrecord/db/enums/AITaskStatus.java
AITaskStatus 열거형 추가(PENDING, IN_PROGRESS, COMPLETED, FAILED)

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant AIFacade as StudentRecordAIFacade
    participant MemberSvc as MemberService
    participant PointSvc as PointService
    participant PointHistRepo as PointHistoryRepository
    participant DB as Database

    Client->>AIFacade: createTaskId(recordId, memberId)

    rect rgb(240,240,255)
      note over AIFacade,MemberSvc: 멤버 조회 (동시성 고려)
      AIFacade->>MemberSvc: getMemberById(memberId)
      MemberSvc-->>AIFacade: Member
    end

    rect rgb(240,255,240)
      note over AIFacade: AI 태스크 생성
      AIFacade->>AIFacade: createAITask() -> taskId
    end

    rect rgb(255,245,230)
      note over AIFacade,PointSvc: 포인트 차감 (신규 흐름)
      AIFacade->>PointSvc: deductPoints(memberId, points, taskId)
      PointSvc->>PointHistRepo: save(PointHistory(DEDUCT, amount, taskId))
      PointHistRepo->>DB: INSERT point_history
      DB-->>PointHistRepo: OK
      PointSvc-->>AIFacade: void
    end

    AIFacade-->>Client: taskId
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 분

특별 주의 필요 영역:

  • PointService 패키지 이동 및 API 시그니처 변경(반환형 void)로 인해 전체 호출 지점 영향 검토
  • point_history 테이블 제약조건(UQ_point_history_task_transaction)과 엔티티 매핑의 NULL 처리/일관성 확인
  • 기존 MemberErrorCode에서 제거된 상수 참조(컴파일 오류 가능성) 탐지
  • 트랜잭션 경계(멤버 락, 포인트 차감, PointHistory 저장) 및 예외 매핑 확인

Possibly related PRs

Suggested reviewers

  • TaegeunYou

Poem

🐰 포인트 밭에 살금살금 걸어가,
새 이력표에 발자욱 톡 쌓아두고,
태스크 만든 뒤에 차감하니 마음도 가벼워,
기록 한 줄, 흐름 한 번 — 토끼가 기뻐 깡총! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "EDMT-458 포인트 히스토리 관리 테이블 추가"는 변경사항의 핵심을 명확하게 요약합니다. 제목이 구체적으로 PointHistory 테이블 추가라는 주요 변경사항을 나타내고 있으며, 간결하고 이해하기 쉬운 표현을 사용하고 있습니다. 팀원이 커밋 히스토리를 스캔할 때 이 PR의 주요 목적을 명확하게 파악할 수 있습니다.
Description Check ✅ Passed PR 설명이 제공된 템플릿의 필수 섹션을 모두 충족하고 있습니다. 📣 Jira Ticket, 👩‍💻 작업 내용, 📝 리뷰 요청 & 논의하고 싶은 내용이 모두 작성되었으며, 각 섹션이 매우 상세하게 작성되어 있습니다. 추가로 Test Plan과 Deployment Notes를 포함하여 더욱 완성도 있는 설명을 제공하고 있으며, 선택 사항인 스크린 샷은 미포함되어 있으나 이는 문제가 되지 않습니다.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/EDMT-458

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63398e0 and 66786ad.

📒 Files selected for processing (1)
  • edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql

Comment @coderabbitai help to get the list of available commands and usage tips.

@kgy1008 kgy1008 changed the title [EDMT-458] 포인트 히스토리 관리 테이블 추가 및 트랜잭션 추적 기능 구현 [EDMT-458] 포인트 히스토리 관리 테이블 추가 Oct 25, 2025
@kgy1008 kgy1008 self-assigned this Oct 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
edukit-core/src/main/java/com/edukit/core/point/db/repository/PointHistoryRepository.java (1)

1-7: 기본 CRUD 시작은 OK. 조회 패턴에 맞춘 파생 메서드와 페이지네이션을 미리 노출하는 것을 권장합니다.

AI 과금/보상 흐름을 고려하면 다음 조회가 자주 필요합니다:

  • taskId+transactionType로 최근 1건 조회(보상 중복 방지)
  • memberId 기준 최신 이력 페이징 정렬(createdAt desc)

예시: findTopByTaskIdAndTransactionTypeOrderByCreatedAtDesc(...), Page findByMemberIdOrderByCreatedAtDesc(...). 서비스 레이어에서 Pageable 기반 API를 노출하면 추후 목록 화면/관리자 조회에 재사용성이 높습니다. 마이그레이션에서 인덱스(task_id, member_id, created_at)를 이미 추가했다면 위 쿼리와 시너지가 납니다.

장기적 데이터 증가에 대비해 보관기간/아카이빙 전략(예: 파티셔닝 또는 백필+압축 테이블)도 미리 고려해 주세요.

edukit-core/src/main/java/com/edukit/core/studentrecord/db/enums/AITaskStatus.java (1)

3-8: 상태 분기 중복을 줄이기 위해 헬퍼 메서드 추가를 권장합니다.

상태 체크가 여러 곳에 생길 수 있어 isTerminal()/isInProgressLike() 등을 제공하면 가독성과 안정성이 좋아집니다.

아래처럼 간단히 확장 가능합니다:

 public enum AITaskStatus {
     PENDING,        // 생성됨
     IN_PROGRESS,    // SQS 전송됨, Lambda 처리 중
     COMPLETED,      // 성공
-    FAILED        // 실패 (보상 트랜잭션 대상)
+    FAILED;        // 실패 (보상 트랜잭션 대상)
+
+    public boolean isTerminal() {
+        return this == COMPLETED || this == FAILED;
+    }
+
+    public boolean isInProgressLike() {
+        return this == PENDING || this == IN_PROGRESS;
+    }
 }
edukit-core/src/main/java/com/edukit/core/point/exception/PointException.java (1)

5-9: 얇은 래퍼로 충분하지만, 원인 전파용 생성자 추가를 고려해 주세요.

운영 중 원인 추적을 위해 cause/추가 메세지 전달 오버로드가 있으면 유용합니다(전제: BusinessException이 해당 시그니처를 지원).

BusinessException 생성자 시그니처를 확인해 주실 수 있을까요? 지원한다면 아래와 같은 오버로드를 추가하는 것을 권장합니다(예시):

public PointException(final PointErrorCode errorCode, final Throwable cause) { super(errorCode, cause); }
public PointException(final PointErrorCode errorCode, final String detail) { super(errorCode, detail); }
edukit-core/src/main/java/com/edukit/core/point/db/enums/PointTransactionType.java (1)

3-9: 필수 확인 사항이 이미 완료되었습니다

PointHistory.java의 35번 줄에서 @Enumerated(EnumType.STRING)이 이미 적용되어 있으므로 ORDINAL로 인한 데이터 오염 위험이 없습니다. ✅


권장: 잔액 증감을 명확히 하는 enum 메서드 추가

각 트랜잭션 타입이 잔액을 증가/감소시키는지가 명시적으로 정의되지 않아 서비스 로직에서 실수 위험이 있습니다. 다음과 같이 헬퍼 메서드를 추가하면 도메인 규칙을 캡슐화할 수 있습니다:

 public enum PointTransactionType {
     CHARGE,          // 충전 (결제 등)
     DEDUCT,          // 차감 (AI 생성 등)
     REFUND,          // 환불
     COMPENSATION,    // 보상 (실패 시 복구)
-    ADMIN_ADJUST     // 관리자 조정
-}
+    ADMIN_ADJUST;    // 관리자 조정
+
+    public boolean increasesBalance() {
+        return this == CHARGE || this == REFUND || this == COMPENSATION;
+    }
+
+    public boolean decreasesBalance() {
+        return this == DEDUCT;
+    }
+}
edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (2)

44-48: 이벤트는 트랜잭션 커밋 이후에 발행하세요.

현재 즉시 발행이라 롤백 시 외부 구독자가 롤백 전 상태를 관측할 수 있습니다. @TransactionalEventListener(phase = AFTER_COMMIT) 또는 TransactionSynchronizationManager#afterCommit로 전환을 권장합니다.


32-32: 하드코딩된 차감 포인트의 설정화.

DEDUCTED_POINTS = 100을 설정값(예: application.yml)으로 이동해 운영 중 조정 가능하도록 하세요.

edukit-core/src/main/java/com/edukit/core/point/db/entity/PointHistory.java (2)

1-24: 매핑 명시화로 네이밍 전략 의존성 제거.

스키마(point_history)와의 1:1 매핑을 명시해 향후 네이밍 전략 변경 리스크를 제거하세요.

적용 diff:

 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
@@
-@Entity
+@Entity
+@Table(name = "point_history")
 public class PointHistory extends BaseTimeEntity {

35-43: 컬럼명 명시 및 도메인 검증(양수 금지) 추가.

  • DB 컬럼명과의 괴리를 방지하려면 transaction_type, task_id를 명시하세요.
  • amount는 양수만 허용하도록 팩토리에서 방어로직을 추가하세요.

적용 diff:

-    @Enumerated(EnumType.STRING)
-    @Column(nullable = false)
+    @Enumerated(EnumType.STRING)
+    @Column(name = "transaction_type", nullable = false)
     private PointTransactionType transactionType;
@@
-    @Column
+    @Column(name = "task_id")
     private Long taskId;
@@
-    public static PointHistory create(final Member member, final PointTransactionType transactionType,
-                                      final int amount, final Long taskId) {
+    public static PointHistory create(final Member member, final PointTransactionType transactionType,
+                                      final int amount, final Long taskId) {
+        if (amount <= 0) {
+            throw new IllegalArgumentException("amount must be positive");
+        }
         return PointHistory.builder()
                 .member(member)
                 .transactionType(transactionType)
                 .amount(amount)
                 .taskId(taskId)
                 .build();
     }

Also applies to: 54-61

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01d65dc and 5e5e188.

📒 Files selected for processing (11)
  • edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (2 hunks)
  • edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/service/PointService.java (0 hunks)
  • edukit-core/src/main/java/com/edukit/core/point/db/entity/PointHistory.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/point/db/enums/PointTransactionType.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/point/db/repository/PointHistoryRepository.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/point/exception/PointErrorCode.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/point/exception/PointException.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/point/service/PointService.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/db/enums/AITaskStatus.java (1 hunks)
💤 Files with no reviewable changes (1)
  • edukit-core/src/main/java/com/edukit/core/member/service/PointService.java
🧰 Additional context used
🧬 Code graph analysis (1)
edukit-core/src/main/java/com/edukit/core/point/service/PointService.java (3)
edukit-core/src/main/java/com/edukit/core/member/exception/MemberException.java (1)
  • MemberException (6-11)
edukit-core/src/main/java/com/edukit/core/point/exception/PointException.java (1)
  • PointException (5-10)
edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (1)
  • Service (21-63)
🔇 Additional comments (3)
edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java (1)

11-18: 리팩터링이 올바르게 완료되었습니다. 잔존 참조 없음.

검증 결과 MemberErrorCode에서 제거된 INSUFFICIENT_POINTS는 PointErrorCode로 깔끔하게 이관되었고, PointService에서 이미 올바른 위치(PointErrorCode.INSUFFICIENT_POINTS)를 참조하고 있습니다. 기존 오류 코드 문자열(M-40009) 또는 레거시 참조도 없어 리팩터링이 완벽하게 완료된 상태입니다.

edukit-core/src/main/java/com/edukit/core/point/exception/PointErrorCode.java (1)

7-14: 문제 없음 (LGTM).

ErrorCode 계약 충족, Lombok 사용 일관적입니다. 현재 범위에선 추가 조치 불필요합니다.

edukit-core/src/main/java/com/edukit/core/point/service/PointService.java (1)

23-38: 입력 검증은 필요하나, 제시된 멱등성 로직은 재검토 필요.

제시된 existsByTransactionTypeAndTaskId() 메서드가 PointHistoryRepository에 존재하지 않으며, 제안된 diff에서도 레포지토리 메서드 구현이 포함되지 않았습니다. 또한 PointHistory 스키마에서 taskId가 nullable이므로, taskId가 null인 경우 제시된 멱등성 검사가 작동하지 않습니다.

  • pointsToDeduct <= 0 입력 검증은 필수입니다(차감이 가산으로 동작하는 문제 방지).
  • ⚠️ 멱등성 가드 로직을 다시 검토하세요:
    • taskId 필드가 nullable인 점을 고려하여, 중복 차감 방지 전략을 재설계하세요.
    • taskId 기반 유일성 보장이 필요하다면, PointHistory 엔티티에 고유 제약 조건 추가 여부와 레포지토리 쿼리 메서드를 함께 정의하세요.

Comment on lines +40 to +52
@Transactional
public Member compensatePoints(final Long memberId, final int pointsToCompensate, final Long taskId) {
Member member = memberRepository.findByIdWithLock(memberId)
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));

member.deductPoints(-pointsToCompensate); // 음수 차감으로 복구

// 포인트 히스토리 기록
PointHistory history = PointHistory.create(member, PointTransactionType.COMPENSATION, pointsToCompensate, taskId);
pointHistoryRepository.save(history);

return member;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

보상 멱등성/검증 보강 및 API 일관성.

  • pointsToCompensate <= 0 검증이 없어 음수 입력 시 추가 차감될 수 있습니다.
  • 동일 taskId에 대한 중복 보상 방지 가드 필요(재시도 시 포인트 과지급 위험).
  • 반환 타입이 차감(void)과 보상(Member)로 불일치합니다(선택) — 일관성 고려.

적용 diff 제안:

-    public Member compensatePoints(final Long memberId, final int pointsToCompensate, final Long taskId) {
+    public Member compensatePoints(final long memberId, final int pointsToCompensate, final Long taskId) {
+        if (pointsToCompensate <= 0) {
+            throw new IllegalArgumentException("pointsToCompensate must be positive");
+        }
+        if (taskId != null
+            && pointHistoryRepository.existsByTransactionTypeAndTaskId(PointTransactionType.COMPENSATION, taskId)) {
+            // 멱등 처리: 이미 보상된 작업이면 재보상하지 않음
+            return memberRepository.findByIdWithLock(memberId)
+                    .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
+        }
         Member member = memberRepository.findByIdWithLock(memberId)
                 .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
 
-        member.deductPoints(-pointsToCompensate); // 음수 차감으로 복구
+        member.deductPoints(-pointsToCompensate); // NOTE: 가능하면 add/increase 메서드로 대체 권장

추가로, 가능하다면 Member.addPoints(int)와 같은 명시적 가산 메서드 도입을 권장합니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In edukit-core/src/main/java/com/edukit/core/point/service/PointService.java
around lines 40–52, add validation and idempotency checks and replace the
negative-deduction hack: verify pointsToCompensate > 0 and throw a
MemberException for invalid input; check pointHistoryRepository (or repository
method) for an existing COMPENSATION record with the same taskId and memberId
and return the current member or throw a specific exception to prevent duplicate
compensation; replace member.deductPoints(-pointsToCompensate) with a clear
member.addPoints(pointsToCompensate) call (introduce addPoints on Member if
needed) and keep returning Member (or change both related APIs to void for
consistency—prefer returning Member here to match this method) while preserving
@Transactional and saving the new PointHistory only when no duplicate exists.

…nd idempotency

- Add composite index (member_id, created_at DESC) for efficient member history queries
- Add unique index (task_id, transaction_type) for idempotency enforcement
- Prevent duplicate transactions for same AI task (MySQL NULL handling allows CHARGE duplicates)
- Remove redundant single-column indexes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql (1)

21-22: 선택사항: task_id 단일 인덱스 재고 고려.

UQ_point_history_task_transaction 유니크 인덱스(라인 19)가 이미 (task_id, transaction_type) 복합 인덱스로 존재하므로, 라인 22의 IDX_point_history_task_id는 task_id 단일 조회에서 추가 성능 이점이 제한적입니다. 쿼리 패턴 분석 후 불필요하면 제거하거나, task_id만으로 필터링하는 조회가 자주 발생한다면 유지해도 무방합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e5e188 and 63398e0.

📒 Files selected for processing (1)
  • edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql (1 hunks)

point_history_id bigint auto_increment primary key,
member_id bigint not null,
transaction_type enum ('CHARGE', 'DEDUCT', 'REFUND', 'COMPENSATION', 'ADMIN_ADJUST') not null,
amount int not null,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

데이터 무결성 강화: amount 제약과 FK 삭제 정책을 명시하세요.

현재 스키마에 두 가지 중요한 제약이 누락되어 있습니다:

  1. amount 양수 제약 부재: 서비스 입력 검증 실패 시 음수 기록이 발생할 수 있어 데이터 오염 위험이 있습니다.
  2. FK ON DELETE 정책 부재: 멤버 삭제 시 히스토리 보존/제약 의도를 명시하지 않아 미정의 동작 초래.

다음 diff를 적용하여 스키마 수준의 보호를 추가하세요:

     constraint FK_point_history_member foreign key (member_id) references member (member_id)
+      on delete restrict
 );
+
+-- amount 양수 제약 (서비스 검증 실패 시 대비)
+alter table point_history
+  add constraint CHK_point_history_amount_positive check (amount > 0);

Also applies to: 11-11

🤖 Prompt for AI Agents
In edukit-api/src/main/resources/db/migration/V11__Add_point_history_table.sql
around line 7, add a schema-level CHECK to enforce amount > 0 and make the
foreign key explicit about delete behavior: change the member FK to use ON
DELETE SET NULL (and alter member_id to be nullable) so history is preserved
when a member is removed, or alternatively use ON DELETE RESTRICT if you want to
prevent member deletion; implement the chosen policy and add the CHECK
constraint for amount to ensure only positive amounts are allowed.

@kgy1008 kgy1008 merged commit f5af824 into develop Oct 25, 2025
2 checks passed
@kgy1008 kgy1008 deleted the feat/EDMT-458 branch October 25, 2025 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants