Skip to content

[FEAT] 채팅 기능 구현 및 개선#68

Open
Sehi55 wants to merge 26 commits into
developfrom
temp
Open

[FEAT] 채팅 기능 구현 및 개선#68
Sehi55 wants to merge 26 commits into
developfrom
temp

Conversation

@Sehi55
Copy link
Copy Markdown
Contributor

@Sehi55 Sehi55 commented May 27, 2026

  • 채팅방 나가기 오류 수정
  • 채팅 보내기 오류 수정

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 채팅 전송/동기화(초기 동기화, 최신/히스토리 조회) 및 실시간 메시지/읽음 이벤트
    • 채팅 내 프로필 사진 요청(요청·수락·거절) 및 상태 알림
    • 위치 기반 만남 인증(검증·레이트컴어 보상)
    • 신고 API(채팅/프로필) 및 신고 데이터 저장
  • Improvements

    • 읽음/언읽음 카운트 정확도 향상 및 구독 기반 위치 추적
    • 채팅방 퇴장·소프트 삭제·멤버 상태 관리 강화

Review Change Stack

@Sehi55 Sehi55 self-assigned this May 27, 2026
@Sehi55 Sehi55 added the bug Something isn't working label May 27, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Walkthrough

이 PR은 채팅 전송·동기화, 읽음·퇴장 처리, 사진 요청(요청/수락/거절), 만남 위치 인증, 신고 기능(엔티티·서비스·컨트롤러) 및 관련 리포지토리와 DB 마이그레이션을 추가·수정합니다.

Changes

통합 기능 변경

Layer / File(s) Summary
채팅 DTO 및 이벤트
manabom/src/main/java/mannabom_server/manabom/application/chat/dto/...
ChatMessageRequest, ChatInitialSyncResponse, ChatRoomListResponse, ChatSyncResponse, ChatHistoryResponse, ChatMessageResponse, ChatReadEvent, ChatRoomLeaveEvent 등 DTO 추가/수정.
도메인 엔티티·열거형
manabom/src/main/java/mannabom_server/manabom/domain/...
ChatMember(status, lastReadMessageId 변경), ChatRoom soft-delete, ChatMessage @Builder/@AllArgsConstructor, ChatMemberStatus, ChatMessageType.getDisplayMessage, Meeting 관련 엔티티(Participant, Verification) 및 상태 필드/메서드 추가.
레포지토리
manabom/src/main/java/mannabom_server/manabom/domain/.../repository/*
ChatMemberRepository/ChatMessageRepository 등 상태 기반/범위/페이징/카운트 쿼리 추가, Meeting/PhotoRequest/Report 리포지토리 시그니처 추가.
채팅 서비스
manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatService.java
sendMessage 구현(메시지 저장, Redis unread 설정, STOMP 브로드캐스트, 알림 전송) 및 동기화/히스토리 조회 구현.
멤버 읽음·채팅방 로직
manabom/src/main/java/mannabom_server/manabom/application/chat/service/*
ChatMemberService.updateReadStatus(아카이브 감소 및 ChatReadEvent 전송), ChatRoomService에 웰컴 메시지 저장과 leave(userId) 변경 및 ChatRoomLeaveEvent 발행.
웹소켓/컨트롤러
manabom/src/main/java/mannabom_server/manabom/infrastructure/security/websocket/*, .../presentation/*/controller/*
StompAuthChannelInterceptor에 CONNECT/SUBSCRIBE/DISCONNECT 인증·Redis 위치 기록 추가, ChatWsController가 ChatService로 위임, ChatApiController/PhotoRequestController/ReportController 추가.
사진 요청 기능
manabom/src/main/java/mannabom_server/manabom/application/matching/*
LoveViewPhotoRequest 엔티티·서비스·컨트롤러 구현: 상태 조회·생성·수락·거절 및 알림 전송 로직.
만남 인증
manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java, global/util/LocationUtils.java
위치 기반 인증 로직(일반/레이트컴어), LocationUtils(Haversine) 도입, 보상 처리 흐름 구현.
신고 기능
manabom/src/main/java/mannabom_server/manabom/application/report/*
Report 엔티티·ReportService.createReport·ReportController·CreateReportRequest DTO 추가.
DB 마이그레이션
src/main/resources/db/migration/V14-V17.sql
V14: chat_members.status, meeting.deleted_at 추가; V15: last_read_message_id → bigint; V16: reports/meeting_verification/meeting_participant/love_view_photo_requests 생성; V17: chat_rooms.deleted_at 추가.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

"🐰" 시적인 축하

나는 당근 대신 코드를 깎네,
방 하나 열리고 메시지 춤추네,
위치는 달력처럼 거리를 재고,
사진 요청은 두근거림 전송하네.
새 엔티티들, 마이그레이션도 함께 축하해!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed PR 제목은 '[FEAT] 채팅 기능 구현 및 개선'이며, 변경사항의 주요 목적인 채팅 기능 구현 및 개선을 명확히 반영하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch temp

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.

❤️ Share

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

Copy link
Copy Markdown

@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: 7

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
manabom/src/main/java/mannabom_server/manabom/presentation/notification/controller/NotificationController.java (1)

28-41: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

테스트 엔드포인트에 인가 검증이 없어 보안 위험이 있습니다.

이 엔드포인트는 요청자가 targetUserId에 알림을 보낼 권한이 있는지 검증하지 않습니다. 악의적인 사용자가 임의의 사용자에게 알림을 스팸으로 보낼 수 있습니다. 프로덕션에 배포되면 심각한 보안 취약점이 됩니다.

권장 조치:

  1. 이 엔드포인트를 개발 환경으로만 제한 (@Profile("dev") 추가)
  2. 또는 관리자 권한 검증 추가
  3. 또는 배포 전 완전히 제거
🛡️ 프로파일 기반 제한 적용 예시
+    `@Profile`("dev")
     /**
      * 배포 전 삭제
      * **/
     `@PostMapping`("/test/send")
     public String sendTestNotification(`@RequestBody` TestNotificationRequest request) {
🤖 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/presentation/notification/controller/NotificationController.java`
around lines 28 - 41, The sendTestNotification test endpoint lacks authorization
checks allowing anyone to trigger notifications; restrict it by either
annotating the controller/method with `@Profile`("dev") to limit it to
development, OR add an authorization guard such as
`@PreAuthorize`("hasRole('ADMIN')") (or an explicit SecurityContextHolder check)
inside sendTestNotification using TestNotificationRequest.targetUserId() before
calling notificationService.sendNotification, and remove or disable the endpoint
prior to production deploy; target symbols: sendTestNotification,
TestNotificationRequest, notificationService, and the `@PostMapping`("/test/send")
method.
🟠 Major comments (20)
manabom/src/main/java/mannabom_server/manabom/application/report/service/ReportService.java-20-26 (1)

20-26: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

입력/인증값 null 방어가 없어 500 오류로 이어질 수 있습니다.

userId 또는 request.getTargetId()가 null인 경우 현재 흐름은 NPE/서버 오류로 떨어질 수 있습니다. 서비스 시작 지점에서 필수값을 명시적으로 검증해 주세요.

🔧 제안 수정안
 `@Transactional`
 public void createReport(Long userId, CreateReportRequest request, ReportType type) {
+    if (userId == null) {
+        throw new IllegalArgumentException("인증 정보가 없습니다.");
+    }
+    if (request == null || request.getTargetId() == null || request.getReason() == null) {
+        throw new IllegalArgumentException("신고 필수 값이 누락되었습니다.");
+    }
     if (userId.equals(request.getTargetId()))
         throw new IllegalArgumentException("자기 자신을 신고할 수 없습니다.");
🤖 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/report/service/ReportService.java`
around lines 20 - 26, Add explicit null checks at the start of createReport:
validate that request, userId, and request.getTargetId() are not null (e.g.,
using Objects.requireNonNull or explicit ifs) and throw a clear
IllegalArgumentException when any are null; then proceed with the existing logic
(including the self-report check and repository lookups using
userRepository.findById) so you avoid NPEs and return a 400-level error instead
of a 500.
manabom/src/main/resources/db/migration/V16__create_tables_report_meetingVerification_profileRequest.sql-22-23 (1)

22-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

검증 플래그 컬럼을 NOT NULL로 고정하세요.

Line 22, Line 31의 is_verified가 nullable이라 NULL 상태가 유입될 수 있습니다. 검증 상태는 이진값으로 고정하는 편이 안전합니다.

수정 제안
-                                     is_verified BOOLEAN DEFAULT FALSE,
+                                     is_verified BOOLEAN NOT NULL DEFAULT FALSE,
-                                     is_verified BOOLEAN DEFAULT FALSE,
+                                     is_verified BOOLEAN NOT NULL DEFAULT FALSE,

Also applies to: 31-31

🤖 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/V16__create_tables_report_meetingVerification_profileRequest.sql`
around lines 22 - 23, The is_verified boolean column definitions (currently
"is_verified BOOLEAN DEFAULT FALSE") should be made NOT NULL to prevent NULL
states; update both occurrences (the is_verified column in the table created
near the top of this diff and the second is_verified occurrence referenced at
line 31) to "is_verified BOOLEAN NOT NULL DEFAULT FALSE" so the column is a
strict binary flag and cannot store NULL values.
manabom/src/main/resources/db/migration/V14__add_column.sql-10-11 (1)

10-11: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

meeting.deleted_at 컬럼 타입을 TIMESTAMPTZ(6)로 통일하세요.

V14__add_column.sqlmeeting.deleted_atTIMESTAMP(6)으로 추가하지만, Meeting.deletedAtInstant이고 V17__add_deleted_at_to_chat_rooms.sqlchat_rooms.deleted_attimestamptz(6)입니다. (타임존 해석/비교/직렬화에서 차이 가능)

수정 제안
-ALTER TABLE meeting ADD COLUMN deleted_at TIMESTAMP(6) NULL;
+ALTER TABLE meeting ADD COLUMN deleted_at TIMESTAMPTZ(6) 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/V14__add_column.sql` around lines 10
- 11, V14__add_column.sql currently adds meeting.deleted_at as TIMESTAMP(6) but
the domain uses Meeting.deletedAt (Instant) and other migrations (e.g.,
chat_rooms.deleted_at in V17) use timestamptz(6); update the ALTER TABLE
statement that creates meeting.deleted_at to use TIMESTAMPTZ(6) NULL instead of
TIMESTAMP(6) to ensure consistent timezone-aware storage for Meeting.deletedAt
and compatibility with existing chat_rooms.deleted_at semantics.
manabom/src/main/resources/db/migration/V17__add_deleted_at_to_chat_rooms.sql-1-2 (1)

1-2: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Flyway 버전드 마이그레이션에서 ADD COLUMN IF NOT EXISTS 제거 권장

  • V17__add_deleted_at_to_chat_rooms.sqldeleted_at 추가가 ADD COLUMN IF NOT EXISTS라서, 컬럼이 이미 존재하더라도(타입/정의가 다른 경우) Flyway가 불일치를 조용히 통과할 수 있습니다.
수정 제안
 ALTER TABLE public.chat_rooms
-ADD COLUMN IF NOT EXISTS deleted_at timestamptz(6) NULL;
+ADD COLUMN deleted_at timestamptz(6) 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/V17__add_deleted_at_to_chat_rooms.sql`
around lines 1 - 2, The migration V17__add_deleted_at_to_chat_rooms.sql should
not use "IF NOT EXISTS" because Flyway must fail on schema drift; replace the
current ALTER TABLE statement with a plain ALTER TABLE public.chat_rooms ADD
COLUMN deleted_at timestamptz(6) NULL; so Flyway will error if the column
already exists with a different definition, or alternatively add an explicit
pre-check (e.g., a DO block / SELECT from information_schema.columns) that
verifies if "deleted_at" exists and has the exact type/nullability and
explicitly RAISE EXCEPTION on mismatch so the migration fails rather than
silently skipping.
manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/Meeting.java-121-124 (1)

121-124: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

마지막 멤버 퇴장 분기에서 점유율 점수가 갱신되지 않습니다.

Line 121~123 분기에서는 currentMembersavgAge만 0으로 바꾸고 occupancyScore를 갱신하지 않아, 점유율 데이터가 이전 값으로 남습니다.

수정 제안
         } else {
             this.currentMembers = 0;
             this.avgAge = 0.0;
+            updateOccupancyScore();
         }
🤖 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/meeting/entity/Meeting.java`
around lines 121 - 124, In Meeting.java's branch that handles the last member
leaving (where it sets this.currentMembers = 0 and this.avgAge = 0.0) also
reset/update the occupancy score so stale occupancy data isn't left behind;
either assign this.occupancyScore = 0.0 (or the appropriate zero value/type) in
that else block or invoke the existing occupancy recalculation method (e.g.,
updateOccupancyScore()/recalculateOccupancy()) so occupancyScore is consistent
with currentMembers and avgAge.
manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingMatch.java-31-36 (1)

31-36: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

@NotFound(IGNORE)로 인해 연관 엔티티 누락 시 NPE 위험

MeetingMatch에서 meeting1/meeting2@NotFound(action = NotFoundAction.IGNORE)가 적용돼 FK 대상 행이 누락되면 해당 필드가 null로 로딩될 수 있습니다. 그런데 reject/accept/autoAccept 및 매칭 성공 경로에서 meeting1.getId(), meeting2.getId(), meeting1.changeToMatchedStatus(), meeting2.changeToMatchedStatus(), determineAutoDecision(meeting1/meeting2) 등을 null 가드 없이 호출하고 있어 런타임 NPE로 이어질 수 있습니다.

수정 제안 (무결성 기반)
-import org.hibernate.annotations.NotFound;
-import org.hibernate.annotations.NotFoundAction;
@@
     `@ManyToOne`(fetch = FetchType.LAZY)
-    `@NotFound`(action = NotFoundAction.IGNORE)
     `@JoinColumn`(name = "meeting1_id", nullable = false)
     private Meeting meeting1;
     `@ManyToOne`(fetch = FetchType.LAZY)
-    `@NotFound`(action = NotFoundAction.IGNORE)
     `@JoinColumn`(name = "meeting2_id", nullable = false)
     private Meeting meeting2;
🤖 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/meeting/entity/MeetingMatch.java`
around lines 31 - 36, MeetingMatch currently uses `@NotFound`(action =
NotFoundAction.IGNORE) on meeting1/meeting2 but methods reject, accept,
autoAccept and the matching-success path call meeting1.getId(),
meeting2.getId(), meeting1.changeToMatchedStatus(),
meeting2.changeToMatchedStatus(), and determineAutoDecision(...) without null
checks, risking NPEs; fix by adding explicit null-guards early (e.g., a private
validateMeetingsPresent() called from reject, accept, autoAccept and the
matching result flow) that checks meeting1 != null && meeting2 != null and
throws a clear IllegalStateException (or a custom DomainInvariantException)
including the MeetingMatch id and which meeting is missing, or alternatively
remove the `@NotFound` IGNORE if you prefer DB integrity enforcement so the fields
are guaranteed non-null—ensure all references to meeting1/meeting2 use the guard
before calling getId(), changeToMatchedStatus(), or determineAutoDecision.
manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java-97-103 (1)

97-103: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

nearbyIdsChatMember PK로 조회하고 있어 성별 판정이 틀어질 수 있습니다.

Line 97의 findAllById(nearbyIds)userId가 아니라 ChatMember.id 기준 조회입니다. 현재 nearbyIds는 Line 90에서 userId를 넣고 있어, hasMale/hasFemale 계산이 잘못될 가능성이 높습니다.

🤖 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/meeting/service/MeetingVerificationService.java`
around lines 97 - 103, The code incorrectly calls
chatMemberRepository.findAllById(nearbyIds) (where nearbyIds holds userIds)
causing wrong gender checks; change the lookup to use the user id field instead
(e.g. use profileRepository.findAllByUserIdIn(nearbyIds) or
chatMemberRepository.findAllByUserIdIn(nearbyIds) if available) and then compute
hasMale/hasFemale from those Profile objects (or from the
ChatMember->User->Profile mapping) instead of calling
profileRepository.findByUser(...) inside the stream; ensure you reference
nearbyIds, replace findAllById(nearbyIds) with the repository method that
queries by user id, and update the stream to inspect profile.getGender() (or
m.getUser().getProfile().getGender()) accordingly.
manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingMatchingService.java-391-393 (1)

391-393: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

핵심 환불 분기가 미구현 상태입니다.

Line 391 조건을 만족해도 Line 392-393이 비어 있어 실제 환불이 발생하지 않습니다. 이 분기는 사용자 금전 처리와 직결되므로 TODO 상태로 두면 안 됩니다.

원하시면 이 분기의 환불 트랜잭션(중복 환불 방지 포함)까지 반영한 구현안을 같이 정리해드릴게요. 이건 별도 이슈로 분리해도 좋습니다.

🤖 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/meeting/service/MeetingMatchingService.java`
around lines 391 - 393, Condition branch in MeetingMatchingService for full
refund is empty; implement the refund flow when
(myMeeting.getCurrentMembers()+opponent.getCurrentMembers())*2 <
myMeeting.getMaxMembers()+opponent.getMaxMembers(). Inside
MeetingMatchingService implement a transactional, idempotent full-refund path:
1) start a DB transaction (e.g., `@Transactional` on the service method), 2) check
and set a persistent "refunded" marker for each involved meeting or matching
record to prevent duplicate refunds, 3) call the payment/refund API (use your
existing PaymentService.refund(...) or add a processFullRefund(myMeeting,
opponent) helper) for each participant or the original transaction owner, 4)
update account/wallet balances and meeting state and persist RefundRecord
entries in RefundRepository, and 5) handle and log failures so refunds are
rolled back on exception. Ensure you reference and update the exact entities
used in the condition (myMeeting.getCurrentMembers(),
opponent.getCurrentMembers(), myMeeting.getMaxMembers(),
opponent.getMaxMembers()) and perform locking/consistency checks before issuing
refunds.
manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java-96-96 (1)

96-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

절반 이상 판정식이 홀수 인원에서 완화됩니다.

Line 96의 nearbyCount >= (totalMembers/2)는 정수 나눗셈이라 총원 3명일 때 1명만으로 조건이 참이 됩니다. “절반 이상” 의도라면 올림 기준이 필요합니다.

수정 제안
-        if(nearbyCount>=(totalMembers/2)){
+        int required = (totalMembers + 1) / 2;
+        if(nearbyCount >= required){
🤖 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/meeting/service/MeetingVerificationService.java`
at line 96, The comparison in MeetingVerificationService using nearbyCount >=
(totalMembers/2) incorrectly uses integer division and relaxes the condition for
odd totalMembers; change the threshold to a proper "half or more" ceiling (e.g.,
compute required = (totalMembers + 1) / 2 or use Math.ceil(totalMembers / 2.0))
and compare nearbyCount >= required so that totals like 3 require 2 nearby
members; update the check where nearbyCount and totalMembers are used in the
verification logic accordingly.
manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java-109-113 (1)

109-113: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

최종 위치 키 만료가 설정되지 않아 레이트컴어 유효기간 정책과 불일치합니다.

Line 110에서 자정까지 시간을 계산하지만, Line 112 저장 시 TTL을 쓰지 않아 FINAL_LOC_KEY가 남습니다. 메시지(“당일 자정”)와 실제 동작이 다릅니다.

수정 제안
-                stringRedisTemplate.opsForValue().set(String.format(FINAL_LOC_KEY, chatRoomId), latitude + "," + longitude);
+                stringRedisTemplate.opsForValue().set(
+                        String.format(FINAL_LOC_KEY, chatRoomId),
+                        latitude + "," + longitude,
+                        timeUntilMidnight
+                );
🤖 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/meeting/service/MeetingVerificationService.java`
around lines 109 - 113, The FINAL_LOC_KEY is stored without a TTL despite
computing timeUntilMidnight; update the save to set an expiry so the key
auto-expires at midnight. Replace the current
stringRedisTemplate.opsForValue().set(...) call (for FINAL_LOC_KEY with
chatRoomId, latitude, longitude) with a call that sets a TTL based on
timeUntilMidnight (e.g., use opsForValue().set(key, value,
timeUntilMidnight.getSeconds(), TimeUnit.SECONDS) or call
stringRedisTemplate.expire(key, timeUntilMidnight) after set) so the stored
location expires at the computed midnight boundary.
manabom/src/main/java/mannabom_server/manabom/application/matching/service/PhotoRequestService.java-79-95 (1)

79-95: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

동시 요청 시 PENDING 요청이 중복 생성될 수 있습니다.

최신 요청 조회 → 저장이 원자적이지 않아 동시 트래픽에서 둘 다 통과할 수 있습니다. 운영 중 중복 상태 데이터가 생길 수 있습니다.

🔧 제안 방향
- Optional<LoveViewPhotoRequest> latestOpt = photoRequestRepository.findTopByHistoryIdOrderByIdDesc(loveViewId);
+ // 1) PESSIMISTIC WRITE 잠금 조회 메서드 도입 또는
+ // 2) DB 유니크 제약(활성 요청 1개 보장) + 예외 처리로 방어
+ // 예: repository에 잠금 조회 추가
+ // `@Lock`(LockModeType.PESSIMISTIC_WRITE)
+ // Optional<LoveViewPhotoRequest> findTopByHistoryIdOrderByIdDesc(Long historyId);
🤖 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/matching/service/PhotoRequestService.java`
around lines 79 - 95, The current non-atomic "find latest → save" flow using
photoRequestRepository.findTopByHistoryIdOrderByIdDesc (latestOpt) can produce
duplicate PENDING rows under concurrent requests; make the operation atomic by
wrapping the request creation method in a transaction and preventing concurrent
passes—either (1) obtain a DB lock on the LoveView/history row before checking
(e.g. add a repository method like LoveViewRepository.findByIdForUpdate or
annotate a finder with PESSIMISTIC_WRITE and call it inside the transactional
method) then re-check latestOpt and save the LoveViewPhotoRequest builder only
if safe, or (2) enforce uniqueness at the database level (add a unique
constraint/partial index preventing multiple PENDING rows per history_id) and
handle the unique-constraint violation when calling photoRequestRepository.save;
reference photoRequestRepository.findTopByHistoryIdOrderByIdDesc,
photoRequestRepository.save, LoveViewPhotoRequest.builder(), and the method that
contains this logic to apply the fix.
manabom/src/main/java/mannabom_server/manabom/application/matching/service/PhotoRequestService.java-79-84 (1)

79-84: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

REJECTED 이후 재요청 쿨다운 규칙이 생성 로직에서 누락되어 있습니다.

상태 조회는 REJECTED + 10메시지 미만을 제한하지만, 생성 로직은 REJECTED면 즉시 재요청을 허용합니다. 규칙 불일치로 우회가 가능합니다.

🔧 제안 수정
         Optional<LoveViewPhotoRequest> latestOpt = photoRequestRepository.findTopByHistoryIdOrderByIdDesc(loveViewId);
         if(latestOpt.isPresent()){
-            if(latestOpt.get().getStatus()!=PhotoRequestStatus.REJECTED){
+            LoveViewPhotoRequest latest = latestOpt.get();
+            if(latest.getStatus()!=PhotoRequestStatus.REJECTED){
                 throw new IllegalStateException("프로필 요청이 중복되었거나 이미 수락된 상태입니다.");
             }
+            int messageCount = chatMessageRepository.countChatMessagesByRoom_IdAndCreatedAtAfter(
+                    room.getId(), latest.getUpdatedAt()
+            );
+            if (messageCount < 10) {
+                throw new IllegalStateException("거절 이후 10마디 이상 대화 후 다시 요청할 수 있습니다.");
+            }
         }
🤖 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/matching/service/PhotoRequestService.java`
around lines 79 - 84, The creation logic currently only blocks when the latest
request is not REJECTED; add the same REJECTED+cooldown check used elsewhere:
after retrieving latestOpt from
photoRequestRepository.findTopByHistoryIdOrderByIdDesc(loveViewId), if
latestOpt.isPresent() then if latest.get().getStatus() !=
PhotoRequestStatus.REJECTED throw the existing IllegalStateException, else if
latest.get().getStatus() == PhotoRequestStatus.REJECTED and
latest.get().getMessageCount() < 10 also throw the same IllegalStateException to
enforce the 10-message cooldown before allowing a new LoveViewPhotoRequest.
manabom/src/main/java/mannabom_server/manabom/application/chat/dto/request/ChatSendRequest.java-9-15 (1)

9-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

STOMP 페이로드 역직렬화 시 ChatSendRequest 바인딩 실패 가능성
ChatWsController.send(...)에서 ChatSendRequest request를 STOMP payload로 역직렬화하는데, DTO가 @AllArgsConstructor만 있고 @NoArgsConstructor/@JsonCreator·@JsonProperty가 없어 런타임에 생성자 파라미터 매핑이 실패할 수 있습니다.

수정 제안
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.NoArgsConstructor;

 import mannabom_server.manabom.domain.chat.enums.ChatMessageType;

 `@Getter`
 `@AllArgsConstructor`
+@NoArgsConstructor
 public class ChatSendRequest {
     private Long roomId;
     private ChatMessageType messageType;
     private String content;
     private String clientMessageId;
 }
🤖 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/chat/dto/request/ChatSendRequest.java`
around lines 9 - 15, ChatSendRequest lacks a Jackson-friendly
constructor/annotations so STOMP payloads bound in ChatWsController.send(...)
can fail; fix by making the DTO deserializable by adding a Lombok
`@NoArgsConstructor` (keep `@AllArgsConstructor`) and either annotate the all-args
constructor with `@JsonCreator` and each parameter with
`@JsonProperty`("roomId")/@JsonProperty("messageType")/@JsonProperty("content")/@JsonProperty("clientMessageId"),
or annotate the fields with `@JsonProperty` so Jackson can map STOMP payload
properties to the ChatSendRequest fields.
manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatRoomListResponse.java-17-17 (1)

17-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

lastMessageAt 응답값이 항상 null이 되는 구현입니다.

Line 17의 필드명이 lastmessageAt로 선언되어 있고, Line 20-26의 빌더에서 시간 필드를 세팅하지 않아 최신 메시지 시간이 누락됩니다. 방 목록 정렬/표시에 직접 영향이 납니다.

수정 예시
@@
-     private Instant lastmessageAt;
+     private Instant lastMessageAt;
@@
     public static ChatRoomListResponse of(ChatRoom room,String roomName, ChatMessage lastMsg, int unreadCount){
         return ChatRoomListResponse.builder()
                 .roomId(room.getId())
                 .roomName(roomName)
                 .lastMessage(lastMsg!=null ? lastMsg.getType().getDisplayMessage(lastMsg.getContent()): null )
+                .lastMessageAt(lastMsg != null ? lastMsg.getCreatedAt() : null)
                 .unreadCount(unreadCount)
                 .build();
     }

Also applies to: 20-26

🤖 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/chat/dto/response/ChatRoomListResponse.java`
at line 17, The DTO field is misspelled as lastmessageAt and the builder never
sets the last message time, causing nulls; rename the field to lastMessageAt (or
add a correctly-cased JSON/serialization alias) and update the builder in
ChatRoomListResponse to set lastMessageAt from the source (e.g., use
chatRoom.getLastMessageAt() or the appropriate lastMessage timestamp) so the
built response includes the latest message time.
manabom/src/main/java/mannabom_server/manabom/domain/chat/entity/ChatMember.java-54-56 (1)

54-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

읽음 포인터가 역행할 수 있습니다.

Line 54-56은 lastReadMessageId를 무조건 덮어써서, 늦게 도착한 이전 이벤트가 최신 읽음 위치를 되돌릴 수 있습니다. 읽음 포인터는 단조 증가로 갱신되어야 합니다.

수정 예시
 public void updateLastReadMessageId(Long messageId){
-    this.lastReadMessageId = messageId;
+    if (messageId == null) return;
+    if (this.lastReadMessageId == null || messageId > this.lastReadMessageId) {
+        this.lastReadMessageId = messageId;
+    }
 }
🤖 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/chat/entity/ChatMember.java`
around lines 54 - 56, updateLastReadMessageId가 전달된 messageId로 무조건 덮어써서 읽음 포인터가
역행할 수 있으므로, ChatMember.updateLastReadMessageId에서 전달된 messageId가 null이 아니고 현재
this.lastReadMessageId보다 큰 경우에만 갱신하도록 변경하세요; 즉, this.lastReadMessageId가 null이면
무조건 설정하고, 그렇지 않으면 messageId > this.lastReadMessageId 일 때만 this.lastReadMessageId
= messageId 하도록 조건부 업데이트 로직을 추가해 역행을 방지합니다.
manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatRoomService.java-106-113 (1)

106-113: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

웰컴 SYSTEM 메시지가 동기화 응답에서 누락됩니다.

여기서는 user(null)로 저장하는데, 현재 sync/history 쿼리들은 JOIN FETCH m.user를 사용합니다. 그래서 이 메시지는 최신 동기화/히스토리 응답에서 필터링됩니다. 쿼리를 LEFT JOIN으로 바꾸거나 SYSTEM 발신자 모델을 분리해야 합니다.

🤖 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/chat/service/ChatRoomService.java`
around lines 106 - 113, The SYSTEM welcome message is saved with user(null) in
sendSystemWelcomeMessage (ChatMessage) which causes it to be omitted by
sync/history queries that use JOIN FETCH m.user; fix by either (A)
introducing/using a dedicated system sender User entity (e.g., a static/system
User record) and save the ChatMessage with that User instead of null (update
sendSystemWelcomeMessage and ensure the system User exists), or (B) change the
repository queries used by sync/history from JOIN FETCH m.user to LEFT JOIN
FETCH m.user so messages with null user are included; pick one approach and
update sendSystemWelcomeMessage or the repository JPQL methods (and any tests)
accordingly.
manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatService.java-219-225 (1)

219-225: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

hasNext 계산값이 응답에 반영되지 않습니다.

Line 225에서 항상 false를 넘기고 있어서, 더 오래된 메시지가 남아 있어도 클라이언트는 페이지 종료로 오인합니다.

패치 예시
-        return buildChatHistoryResponse(roomId, messages, false);
+        return buildChatHistoryResponse(roomId, messages, hasNext);
🤖 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/chat/service/ChatService.java`
around lines 219 - 225, The response always passes false for the pagination
flag, so clients never see hasNext; use the previously computed boolean by
changing the call buildChatHistoryResponse(roomId, messages, false) to
buildChatHistoryResponse(roomId, messages, hasNext) (ensure hasNext is computed
before you possibly trim messages with messages = messages.subList(0, PAGE_SIZE)
and keep the existing Collections.reverse(messages) logic).
manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatMemberService.java-27-53 (1)

27-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

읽음 처리가 Redis만 줄이고 멤버 상태를 저장하지 않습니다.

lastReadMessageId를 갱신하지 않아서 이후 room list / initial sync가 계속 예전 기준으로 unread를 다시 계산합니다.

패치 예시
         for (ChatMessage m : unreadMessages) {
             String key = UNREAD_PREFIX + m.getId();
             Long count = stringRedisTemplate.opsForValue().decrement(key);
             if (count != null && count < 0) {
                 stringRedisTemplate.opsForValue().set(key, "0");
             }
         }
+
+        chatMember.updateLastReadMessageId(lastSeenMessageId);
 
         ChatReadEvent readEvent = ChatReadEvent.builder()
                 .roomId(roomId)
🤖 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/chat/service/ChatMemberService.java`
around lines 27 - 53, The method updateReadStatus currently decrements Redis
unread counts but never persists the member's lastReadMessageId; update the
ChatMember entity's lastReadMessageId (use
chatMember.setLastReadMessageId(lastSeenMessageId)) and persist it via
chatMemberRepository (e.g., save or saveAndFlush) after the Redis decrement loop
so room list/initial sync compute correct unread state; keep existing
early-return (if lastSeenMessageId <= lastReadId) and use the existing
chatMember variable to locate the record to update.
manabom/src/main/java/mannabom_server/manabom/domain/chat/repository/ChatMessageRepository.java-27-31 (1)

27-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

동기화/히스토리 JPQL 경계 조건이 실제 코드와 맞지 않습니다.

  • findChatMessagesAfter JPQL이 :lastMessageId를 참조하는데 메서드 파라미터는 @Param("lastReadId")라 바인딩이 깨집니다.
  • findChatMessagesBefore인데 비교 연산자가 m.id > :firstMessageId로 되어 있어 “이전(과거)” 히스토리 대신 더 최신 메시지를 가져올 수 있습니다(경계는 m.id < :firstMessageId로 정리 필요).
패치 예시
-    `@Query`("SELECT m FROM ChatMessage m JOIN FETCH m.user u JOIN Profile p on p.user.userId = u.userId WHERE m.room.id = :roomId AND m.id > :lastMessageId ORDER BY m.id desc ")
+    `@Query`("SELECT m FROM ChatMessage m JOIN FETCH m.user u JOIN Profile p ON p.user.userId = u.userId WHERE m.room.id = :roomId AND m.id > :lastReadId ORDER BY m.id DESC")
     List<ChatMessage> findChatMessagesAfter(`@Param`("roomId") Long roomId, `@Param`("lastReadId") Long lastReadId, Pageable pageable);

-    `@Query`("SELECT m FROM ChatMessage m JOIN FETCH m.user u JOIN Profile p on p.user.userId = u.userId WHERE m.room.id = :roomId AND m.id > :firstMessageId ORDER BY m.id desc ")
+    `@Query`("SELECT m FROM ChatMessage m JOIN FETCH m.user u JOIN Profile p ON p.user.userId = u.userId WHERE m.room.id = :roomId AND m.id < :firstMessageId ORDER BY m.id DESC")
     List<ChatMessage> findChatMessagesBefore(`@Param`("roomId") Long roomId, `@Param`("firstMessageId") Long firstMessageId, Pageable pageable);
🤖 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/chat/repository/ChatMessageRepository.java`
around lines 27 - 31, The JPQL parameter name in findChatMessagesAfter doesn't
match its `@Param` and the before-query uses the wrong comparison operator: update
the query for findChatMessagesAfter to reference :lastReadId (or change the
`@Param` to "lastMessageId" so the JPQL and `@Param` match) and update
findChatMessagesBefore's JPQL comparison from m.id > :firstMessageId to m.id <
:firstMessageId so it returns earlier messages; ensure the method signatures
findChatMessagesAfter and findChatMessagesBefore keep their `@Param` names
consistent with the JPQL placeholders.
manabom/src/main/java/mannabom_server/manabom/presentation/chat/controller/ChatApiController.java-53-61 (1)

53-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

동기화 조회 API에서 GET 바디 제거 및 트레일링 슬래시 매핑 정규화 필요

  • @GetMapping@RequestBody를 두면 HTTP GET의 본문 미사용(권장 위반)으로 인해 일부 클라이언트/프록시가 바디를 누락해 400 등 예측 불가 동작이 발생할 수 있습니다.
  • @GetMapping("/sync/chat/{roomId}/")처럼 끝에 /가 붙은 매핑은 Spring Boot 3의 기본 URL 매칭이 엄격해서, 슬래시 없는 호출은 404로 이어질 수 있습니다.

(대상) ChatApiController.java 라인 53-61

수정 예시 diff
-    `@GetMapping`("/sync/chat/{roomId}/")
-    public ResponseEntity<ApiResponse<ChatSyncResponse>> getLatestChatMessagesList(`@PathVariable` Long roomId, `@AuthenticationPrincipal` Long userId, `@RequestBody` ChatMessageRequest request){
-        ChatSyncResponse response = chatService.getLatestChatMessageListSync(roomId, userId, request.getLastReadMessageId());
+    `@GetMapping`("/sync/chat/{roomId}")
+    public ResponseEntity<ApiResponse<ChatSyncResponse>> getLatestChatMessagesList(
+            `@PathVariable` Long roomId,
+            `@AuthenticationPrincipal` Long userId,
+            `@RequestParam`("lastReadMessageId") Long lastReadMessageId) {
+        ChatSyncResponse response = chatService.getLatestChatMessageListSync(roomId, userId, lastReadMessageId);
         return ResponseEntity.ok(ApiResponse.success(response,"최신 채팅 메시지 리스트 동기화 완료했습니다."));
     }

     `@GetMapping`("/history/chat/{roomId}")
-    public ResponseEntity<ApiResponse<ChatHistoryResponse>> getChatMessagesListHistory(`@PathVariable` Long roomId, `@AuthenticationPrincipal` Long userId, `@RequestBody` ChatMessageRequest request){
-        ChatHistoryResponse response = chatService.getChatHistory(roomId, userId, request.getLastReadMessageId());
+    public ResponseEntity<ApiResponse<ChatHistoryResponse>> getChatMessagesListHistory(
+            `@PathVariable` Long roomId,
+            `@AuthenticationPrincipal` Long userId,
+            `@RequestParam`("lastReadMessageId") Long lastReadMessageId) {
+        ChatHistoryResponse response = chatService.getChatHistory(roomId, userId, lastReadMessageId);
         return ResponseEntity.ok(ApiResponse.success(response,"과거 채팅 메시지 조회 완료했습니다."));
     }
🤖 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/presentation/chat/controller/ChatApiController.java`
around lines 53 - 61, The GET handlers getLatestChatMessagesList and
getChatMessagesListHistory must not accept a request body and their mappings
should not end with a trailing slash; change the `@GetMapping` paths from
"/sync/chat/{roomId}/" to "/sync/chat/{roomId}" (and ensure
"/history/chat/{roomId}" is consistent), remove the `@RequestBody`
ChatMessageRequest parameter, add a `@RequestParam` Long lastReadMessageId (e.g.
`@RequestParam`(name="lastReadMessageId", required=false) Long lastReadMessageId)
to each method signature, and pass that lastReadMessageId into
chatService.getLatestChatMessageListSync(...) and
chatService.getChatHistory(...). Ensure the method parameter list references
`@AuthenticationPrincipal` Long userId and the `@PathVariable` Long roomId remain
unchanged.
🟡 Minor comments (1)
manabom/src/main/java/mannabom_server/manabom/presentation/matching/controller/PhotoRequestController.java-42-42 (1)

42-42: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

거절 API 성공 메시지가 의미상 반대로 작성되어 있습니다.

거절 처리 후 "거절을 수락하였습니다"로 응답되어 사용자 혼동이 발생합니다.

🔧 제안 수정
-        return ResponseEntity.ok(ApiResponse.success(null, "프로필 사진 거절을 수락하였습니다."));
+        return ResponseEntity.ok(ApiResponse.success(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/java/mannabom_server/manabom/presentation/matching/controller/PhotoRequestController.java`
at line 42, The success message in PhotoRequestController is reversed: update
the ApiResponse.success call that currently returns "프로필 사진 거절을 수락하였습니다." to a
correct rejection confirmation such as "프로필 사진 거절을 완료하였습니다." (or "프로필 사진 거절을
처리하였습니다."). Locate the ResponseEntity.ok(ApiResponse.success(null, "..."))
invocation in PhotoRequestController and replace the message string accordingly
so the response accurately reflects the rejection outcome.
🧹 Nitpick comments (3)
manabom/src/main/java/mannabom_server/manabom/presentation/notification/controller/NotificationController.java (1)

25-27: ⚡ Quick win

프로파일 기반 활성화로 테스트 엔드포인트를 보호하세요.

주석으로 삭제를 안내하는 것은 좋지만, 실수로 배포될 위험이 있습니다. @Profile("!prod") 또는 @Profile("dev")를 사용하여 프로덕션 환경에서는 이 엔드포인트가 자동으로 비활성화되도록 하는 것이 더 안전합니다.

🔒 프로파일 기반 보호 적용 예시
+    `@Profile`("!prod")  // 또는 `@Profile`({"dev", "test"})
     /**
      * 배포 전 삭제
      * **/
     `@PostMapping`("/test/send")
🤖 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/presentation/notification/controller/NotificationController.java`
around lines 25 - 27, Add a Spring profile restriction so the temporary test
endpoint(s) in the NotificationController are disabled in production: annotate
the NotificationController class (or the specific test handler methods) with
`@Profile`("!prod") or `@Profile`("dev"), import
org.springframework.context.annotation.Profile, and keep the existing comment
but remove the risk of accidental deployment by ensuring the controller (or
methods) are only active under non-prod profiles.
manabom/src/main/resources/db/migration/V16__create_tables_report_meetingVerification_profileRequest.sql (1)

14-15: ⚡ Quick win

FK 컬럼 인덱스를 추가해 조회/정합성 작업 비용을 줄이세요.

현재 FK 제약만 있고 참조 컬럼 인덱스가 없어, 부모 삭제/업데이트 검증 및 조인 성능이 급격히 떨어질 수 있습니다.

수정 제안
+CREATE INDEX idx_reports_reporter_id ON reports(reporter_id);
+CREATE INDEX idx_reports_target_id ON reports(target_id);
+
+CREATE INDEX idx_meeting_verification_room_id ON meeting_verification(room_id);
+
+CREATE INDEX idx_meeting_participant_chat_member_id ON meeting_participant(chat_member_id);
+
+CREATE INDEX idx_photo_requests_history_id ON love_view_photo_requests(history_id);
+CREATE INDEX idx_photo_requests_sender_id ON love_view_photo_requests(sender_id);
+CREATE INDEX idx_photo_requests_receiver_id ON love_view_photo_requests(receiver_id);

Also applies to: 24-24, 33-33, 45-47

🤖 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/V16__create_tables_report_meetingVerification_profileRequest.sql`
around lines 14 - 15, The foreign key constraints (fk_report_reporter,
fk_report_target) are defined without indexes on the referenced columns
(reporter_id, target_id) which hurts delete/update validation and join
performance; add explicit indexes for each FK column in this migration (e.g.,
CREATE INDEX idx_report_reporter_id ON report(reporter_id); CREATE INDEX
idx_report_target_id ON report(target_id)) and likewise add indexes for every
other FK column introduced later in this file (the FK columns mentioned around
lines 24, 33, and 45-47) so that each FK has a supporting index.
manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingParticipant.java (1)

26-35: ⚡ Quick win

필드명 오타(rewaredAt)는 지금 정리하는 게 안전합니다.

Line 26, 34의 rewaredAtrewardedAt 오타로 보이며, 지금 두면 엔티티/컬럼 네이밍이 계속 전파됩니다.

수정 제안
-    private Instant rewaredAt;
+    private Instant rewardedAt;
@@
-        this.rewaredAt = Instant.now();
+        this.rewardedAt = Instant.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/meeting/entity/MeetingParticipant.java`
around lines 26 - 35, Rename the misspelled field rewaredAt to rewardedAt in the
MeetingParticipant entity and update all usages (the field declaration, the
verify() method which sets it, any getters/setters, Builder references and any
places referencing MeetingParticipant.rewaredAt); also update JPA/serialization
annotations if present (e.g., `@Column`(name=...), `@JsonProperty`) to keep the
DB/JSON contract stable or add a migration/column rename if the DB column is
already persisted; ensure tests/other classes that reference the old name are
updated accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f5a7a42-542b-4376-b750-7c41f73d3ed7

📥 Commits

Reviewing files that changed from the base of the PR and between bb07555 and 9a34f66.

📒 Files selected for processing (59)
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/event/ChatRoomLeaveEvent.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/request/ChatMessageRequest.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/request/ChatSendRequest.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatHistoryResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatInitialSyncResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatMemberInfo.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatMessageEvent.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatMessageResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatReadEvent.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatRoomListResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/dto/response/ChatSyncResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/handler/MeetingChatEventHandler.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatMemberService.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatRoomService.java
  • manabom/src/main/java/mannabom_server/manabom/application/chat/service/ChatService.java
  • manabom/src/main/java/mannabom_server/manabom/application/matching/dto/response/LoveViewPhotoStatusResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/matching/service/PhotoRequestService.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingMatchingService.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingMemberService.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingService.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java
  • manabom/src/main/java/mannabom_server/manabom/application/report/dto/request/CreateReportRequest.java
  • manabom/src/main/java/mannabom_server/manabom/application/report/service/ReportService.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/entity/ChatMember.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/entity/ChatMessage.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/entity/ChatRoom.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/enums/ChatMemberStatus.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/enums/ChatMessageType.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/repository/ChatMemberRepository.java
  • manabom/src/main/java/mannabom_server/manabom/domain/chat/repository/ChatMessageRepository.java
  • manabom/src/main/java/mannabom_server/manabom/domain/matching/entity/LoveViewPhotoRequest.java
  • manabom/src/main/java/mannabom_server/manabom/domain/matching/enums/LoveViewPhotoStatus.java
  • manabom/src/main/java/mannabom_server/manabom/domain/matching/enums/PhotoRequestStatus.java
  • manabom/src/main/java/mannabom_server/manabom/domain/matching/repository/LoveViewPhotoRequestRepository.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/Meeting.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingMatch.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingMember.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingParticipant.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingVerification.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/enums/SseEventName.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/repository/MeetingMemberRepository.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/repository/MeetingParticipantRepository.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/repository/MeetingVerificationRepository.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/entity/Report.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/entity/ReportReason.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/entity/ReportStatus.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/entity/ReportType.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/repository/ReportRepository.java
  • manabom/src/main/java/mannabom_server/manabom/global/util/LocationUtils.java
  • manabom/src/main/java/mannabom_server/manabom/infrastructure/security/websocket/StompAuthChannelInterceptor.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/chat/controller/ChatApiController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/chat/controller/ChatWsController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/matching/controller/PhotoRequestController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/notification/controller/NotificationController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/report/controller/ReportController.java
  • manabom/src/main/resources/db/migration/V14__add_column.sql
  • manabom/src/main/resources/db/migration/V15__alter_chat_member_last_read_id.sql
  • manabom/src/main/resources/db/migration/V16__create_tables_report_meetingVerification_profileRequest.sql
  • manabom/src/main/resources/db/migration/V17__add_deleted_at_to_chat_rooms.sql

@Sehi55 Sehi55 changed the title Temp [FEAT] 채팅 기능 구현 및 개선 May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant