Skip to content

[FEAT] 연애관 채팅 프로필 공개 요청 구현#71

Open
Sehi55 wants to merge 26 commits into
developfrom
feat/loveview-profile-request
Open

[FEAT] 연애관 채팅 프로필 공개 요청 구현#71
Sehi55 wants to merge 26 commits into
developfrom
feat/loveview-profile-request

Conversation

@Sehi55

@Sehi55 Sehi55 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

변경 사항

  • 러브뷰 채팅방에서 프로필 공개 요청 API 추가
  • 메시지 10개 이상인 경우에만 프로필 공개 요청 가능하도록 검증 추가
  • 프로필 공개 요청/수락/거절 알림 이벤트 저장을 위해 notification 타입 제약 확장

주요 구현

  • POST /api/loveview/profile/request/{chatRoomId} 추가
  • 프로필 요청 생성 전 현재 상태가 READY인지 검증
  • PHOTO_REQUEST_RECEIVED, PHOTO_REQUEST_ACCEPTED, PHOTO_REQUEST_REJECTED 이벤트 저장 가능하도록 DB 제약 수정

Summary by CodeRabbit

  • New Features
    • 채팅방에서 프로필 사진 공개 요청(요청 가능 상태에서만 가능) 및 요청 상태를 확인할 수 있는 기능이 추가되었습니다.
    • 좌표 기반 만남 인증 요청/인증 상태 조회 API가 추가되었습니다.
    • 관리자 신고/CS 처리 화면이 추가되어 신고 목록·상세·처리 및 정지 만료 시각 입력/표시를 지원합니다.
  • Bug Fixes
    • 프로필 사진 요청 거절 응답 문구를 수정했습니다.
  • Improvements
    • 알림 분류 저장 및 유효성 제약이 개선되어 알림 타입 호환성이 향상되었습니다.
    • 정지 만료 시각 기반으로 관리자 표시/접근 차단 판정이 정확해졌습니다.

kimjuneon and others added 16 commits February 21, 2026 12:47
[Feature] 사용자 사진 관리 기능 구현 및 GCP 배포 전환
[Fix] 배포 시 이미지 환경변수 전달 방식 수정
이미지 계산 방식 개선
[Fix] 배포 단계에서 이미지 주소를 직접 생성
fix: 배포 시 compose 환경변수 로드 보강
fix: compose 실행 시 이미지 변수를 직접 전달
호감/메시지 응답 흐름 및 상대 프로필 과금 로직 개선
관리자 콘솔 및 운영 관리 기능 추가
관리자 회원 사진과 멤버십 표시 수정
운영 환경 SQL 로그 비활성화
수동 배포 실행 버튼 추가
채팅 서비스 구현 및 개선
@Sehi55 Sehi55 self-assigned this Jul 1, 2026
@Sehi55 Sehi55 added the enhancement New feature or request label Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a24208cd-4be7-4e07-b511-9fe6646a0ef1

📥 Commits

Reviewing files that changed from the base of the PR and between 09cceea and 7de76eb.

📒 Files selected for processing (4)
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java
  • manabom/src/main/java/mannabom_server/manabom/infrastructure/redis/meetingmatching/RedisMatchAtomicOps.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/controller/MeetingVerificationController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/dto/MeetingVerificationRequest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/dto/MeetingVerificationRequest.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/controller/MeetingVerificationController.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java

Walkthrough

프로필 사진 요청 생성 전 상태 검사가 추가되었고, 알림 타입은 문자열 enum 저장으로 바뀌었습니다. 관리자 신고 처리와 사용자 정지 만료 시각 반영이 확장되었으며, 만남 인증은 시작·만료 시각과 위치 클러스터 기준으로 재구성되었습니다.

Changes

프로필 사진 요청 및 알림 타입 저장

Layer / File(s) Summary
사진 요청 생성 상태 확인
manabom/src/main/java/.../PhotoRequestService.java, manabom/src/main/java/.../PhotoRequestController.java
사진 요청 생성 전에 현재 요청 가능 상태를 확인하고, 생성 엔드포인트와 성공 메시지를 추가했습니다.
알림 타입 문자열 저장 전환
manabom/src/main/java/.../Notification.java, manabom/src/main/resources/db/migration/V19__change_notification_type_check.sql
Notification.type이 문자열 enum으로 저장되도록 바뀌고, 컬럼 타입 전환과 값 제약 마이그레이션이 추가되었습니다.

관리자 신고 처리와 사용자 정지 상태

Layer / File(s) Summary
정지 만료 상태 저장과 판정
manabom/src/main/java/.../UserAccountRestriction.java, manabom/src/main/java/.../AdminUserService.java, manabom/src/main/java/.../UserAccountAccessGuardFilter.java, manabom/src/main/resources/db/migration/V18__add_admin_report_cs_support.sql, manabom/src/main/java/.../AdminUserSummaryResponse.java, manabom/src/main/java/.../AdminUserDetailResponse.java, manabom/src/main/java/.../AdminUpdateUserStatusRequest.java
사용자 정지 만료 시각이 저장·조회·검증·차단 판정에 반영되도록 관련 도메인, 서비스, DTO, 필터, 스키마가 확장되었습니다.
신고 조회와 처리 서비스
manabom/src/main/java/.../AdminReportService.java, manabom/src/main/java/.../AdminReportController.java, manabom/src/main/java/.../ReportRepository.java, manabom/src/main/java/.../Report.java, manabom/src/main/java/.../AdminProcessReportRequest.java, manabom/src/main/java/.../AdminReportDetailResponse.java, manabom/src/main/java/.../AdminReportListResponse.java, manabom/src/main/java/.../AdminReportSummaryResponse.java, manabom/src/main/java/.../AdminAuditService.java, manabom/src/main/java/.../AdminAuditActionType.java, manabom/src/main/java/.../AdminAuditTargetType.java
관리자 신고 목록·상세·처리 API와 DTO, 처리 중 계정 상태/지갑 조정, 감사 로그 기록, 관련 enum 확장이 추가되었습니다.
관리자 신고 화면
manabom/src/main/resources/static/admin/index.html, manabom/src/main/resources/static/admin/app.js, manabom/src/main/resources/static/admin/styles.css
관리자 화면에 신고 탭, 목록/상세/처리 폼, 상태·정지 만료 입력, 관련 렌더링과 스타일이 추가되었습니다.

만남 매칭과 인증 흐름

Layer / File(s) Summary
매칭 이벤트와 Redis 락
manabom/src/main/java/.../MatchingEventListener.java, manabom/src/main/java/.../RedisMatchAtomicOps.java, manabom/src/main/java/.../RedisMatchHistory.java, manabom/src/main/java/.../RedisMatchQueue.java, manabom/src/main/java/.../MeetingMatchingService.java
매칭 이벤트 실행 시점과 Redis 락/히스토리/큐 처리 방식이 Redisson 및 가변 리스트 기준으로 조정되었습니다.
인증 기간과 상태 모델
manabom/src/main/java/.../MeetingVerification.java, manabom/src/main/java/.../MeetingVerificationService.java, manabom/src/main/resources/db/migration/V20__add_meeting_verification_valid_window.sql, manabom/src/main/java/.../MeetingMatch.java
만남 인증의 시작·만료 시각, 위치 수집, 클러스터 판정, 상태 응답, 생성 시각 채우기 방식이 바뀌었습니다.
인증 API와 요청 응답
manabom/src/main/java/.../MeetingVerificationController.java, manabom/src/main/java/.../MeetingVerificationRequest.java, manabom/src/main/java/.../MeetingVerificationResponse.java
만남 인증 요청과 상태 조회를 위한 API와 요청·응답 DTO가 추가되었습니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • mannabom/mannabomServer#68: PhotoRequestService.createPhotoRequest(...)PhotoRequestController의 같은 기능 경로를 확장한 변경이어서 직접 이어집니다.
  • mannabom/mannabomServer#69: AdminReportController, AdminReportService, 신고 DTO와 관리자 UI가 같은 신고 처리 영역을 다룹니다.
  • mannabom/mannabomServer#70: 사진 요청 흐름의 조회/수락/거절 경로와 같은 코드 영역을 공유합니다.

Poem

나는 작은 토끼, 화면을 폴짝 넘고 🐰
신고도 받고, 사진도 요청하고
정지 시각은 차분히 적어 두고
만남 인증은 별빛처럼 맞춰 가요
알림은 이제 글자 옷을 입었답니다 ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 연애관 채팅에서 프로필 공개 요청 기능 추가를 정확히 요약해 변경 내용과 잘 맞습니다.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/loveview-profile-request

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
manabom/src/main/java/mannabom_server/manabom/application/matching/service/PhotoRequestService.java (1)

73-92: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

중복 쿼리 및 도달 불가능한 분기 정리 필요

createPhotoRequest에서 getPhotoRequestStatus(roomId, userId)를 호출하면 내부적으로 findRoomById, validateLoveViewChatRoom, validateActiveMember, photoRequestRepository.findTopByHistoryIdOrderByIdDesc가 다시 실행됩니다. 이는 이미 73-77번 줄에서 수행한 조회/검증과 중복되어 트랜잭션당 불필요한 DB 조회가 두 배로 늘어납니다.

또한 getPhotoRequestStatus의 로직(라인 54-68)을 따라가 보면, currentStatus == LoveViewPhotoStatus.READY가 되는 경우는 latestOpt가 비어있거나 상태가 REJECTED이면서 메시지 수가 10개 이상인 경우뿐입니다. 즉 READY 검증(79-82번 줄)을 통과한 이후에는 88-92번 줄의 latestOpt.get().getStatus() != PhotoRequestStatus.REJECTED 조건이 항상 거짓이 되어 해당 분기가 도달 불가능한 죽은 코드가 됩니다.

getPhotoRequestStatus가 사용한 latestOpt를 반환하거나 재사용할 수 있도록 리팩터링하여 중복 조회를 없애고, 도달 불가능해진 88-92번 줄의 검증을 정리하는 것을 권장합니다.

♻️ 리팩터링 방향 예시
-        LoveViewPhotoStatus currentStatus = getPhotoRequestStatus(roomId, userId);
-        if (currentStatus != LoveViewPhotoStatus.READY) {
-            throw new IllegalStateException("아직 프로필 사진을 요청할 수 있는 상태가 아닙니다.");
-        }
-
-        LoveViewRecommendHistory loveView = room.getLoveView();
-        Long loveViewId = loveView.getId();
-
-        Optional<LoveViewPhotoRequest> latestOpt = photoRequestRepository.findTopByHistoryIdOrderByIdDesc(loveViewId);
-        if(latestOpt.isPresent()){
-            if(latestOpt.get().getStatus()!=PhotoRequestStatus.REJECTED){
-                throw new IllegalStateException("프로필 요청이 중복되었거나 이미 수락된 상태입니다.");
-            }
-        }
+        LoveViewRecommendHistory loveView = room.getLoveView();
+        Long loveViewId = loveView.getId();
+        Optional<LoveViewPhotoRequest> latestOpt = photoRequestRepository.findTopByHistoryIdOrderByIdDesc(loveViewId);
+        LoveViewPhotoStatus currentStatus = resolveStatus(room, userId, latestOpt);
+        if (currentStatus != LoveViewPhotoStatus.READY) {
+            throw new IllegalStateException("아직 프로필 사진을 요청할 수 있는 상태가 아닙니다.");
+        }

(위와 같이 상태 판별 로직을 latestOpt 재사용이 가능한 private 메서드로 추출하고, getPhotoRequestStatuscreatePhotoRequest 모두에서 활용하도록 리팩터링을 제안합니다.)

🤖 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 73 - 92, `createPhotoRequest` has duplicated
room/member/latest-request lookups because `getPhotoRequestStatus(roomId,
userId)` re-runs the same queries already done earlier, and the later
`latestOpt` status check is dead code once `READY` has been verified. Refactor
`PhotoRequestService#createPhotoRequest` and `getPhotoRequestStatus` to share
the same fetched `LoveViewPhotoRequest`/status data (for example by extracting a
private helper that returns both status and latest request), remove the repeated
repository call, and delete the unreachable `latestOpt.get().getStatus() !=
PhotoRequestStatus.REJECTED` branch.
manabom/src/main/resources/db/migration/V18__extend_notification_type_check.sql (1)

1-9: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

enum 순서 의존성을 줄이세요

Notification.type가 ordinal로 DB에 저장되고 이 CHECK도 그 값에 맞춰져 있습니다. 지금은 맞지만, SseEventName의 순서 변경이나 중간 삽입이 곧바로 스키마 불일치로 이어지므로 고정 코드값이나 STRING 매핑으로 바꾸는 편이 안전합니다.

🤖 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/V18__extend_notification_type_check.sql`
around lines 1 - 9, `notification_type_check` and the `Notification.type`
mapping currently depend on `SseEventName` ordinals, so a future enum reorder or
insertion will break schema consistency. Update the persistence/migration design
used by `Notification.type` and the related enum mapping to use stable explicit
codes or STRING-based storage instead of ordinal values, and adjust the
`notification_type_check` constraint to validate those stable values rather than
a positional range.
🤖 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/resources/db/migration/V18__extend_notification_type_check.sql`:
- Around line 7-9: `notification.type` is still tied to enum ordinal storage,
and the current CHECK constraint only allows a positional range, so enum
reordering or inserts can silently change meaning. Update the `Notification`
mapping to stop using ordinal semantics by switching `type` to
`@Enumerated(EnumType.STRING)` or a fixed `int code` with a converter, then
adjust the `notification_type_check` migration/constraint to match the new
stable representation instead of `0..9`.

---

Nitpick comments:
In
`@manabom/src/main/java/mannabom_server/manabom/application/matching/service/PhotoRequestService.java`:
- Around line 73-92: `createPhotoRequest` has duplicated
room/member/latest-request lookups because `getPhotoRequestStatus(roomId,
userId)` re-runs the same queries already done earlier, and the later
`latestOpt` status check is dead code once `READY` has been verified. Refactor
`PhotoRequestService#createPhotoRequest` and `getPhotoRequestStatus` to share
the same fetched `LoveViewPhotoRequest`/status data (for example by extracting a
private helper that returns both status and latest request), remove the repeated
repository call, and delete the unreachable `latestOpt.get().getStatus() !=
PhotoRequestStatus.REJECTED` branch.

In
`@manabom/src/main/resources/db/migration/V18__extend_notification_type_check.sql`:
- Around line 1-9: `notification_type_check` and the `Notification.type` mapping
currently depend on `SseEventName` ordinals, so a future enum reorder or
insertion will break schema consistency. Update the persistence/migration design
used by `Notification.type` and the related enum mapping to use stable explicit
codes or STRING-based storage instead of ordinal values, and adjust the
`notification_type_check` constraint to validate those stable values rather than
a positional range.
🪄 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

Run ID: 0166a592-05d5-4144-8f0b-2afc688a5112

📥 Commits

Reviewing files that changed from the base of the PR and between e8a28f3 and b669f43.

📒 Files selected for processing (3)
  • manabom/src/main/java/mannabom_server/manabom/application/matching/service/PhotoRequestService.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/matching/controller/PhotoRequestController.java
  • manabom/src/main/resources/db/migration/V18__extend_notification_type_check.sql

Comment thread manabom/src/main/resources/db/migration/V18__extend_notification_type_check.sql Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
manabom/src/main/resources/db/migration/V19__change_notification_type_check.sql (1)

1-17: 🚀 Performance & Scalability | 🔵 Trivial

대용량 테이블에서 ALTER COLUMN TYPE은 전체 재작성 + 배타적 락을 유발

USING 절을 사용한 컬럼 타입 변경은 Postgres에서 테이블 전체를 재작성하며 ACCESS EXCLUSIVE 락을 획득합니다. notification 테이블 규모가 커질 경우 배포 시 다운타임을 유발할 수 있으니, 트래픽이 적은 시간대에 실행하거나 테이블 크기를 사전에 확인하는 것을 권장합니다.

🤖 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/V19__change_notification_type_check.sql`
around lines 1 - 17, The V19 notification migration changes notification.type
with ALTER COLUMN TYPE and a USING CASE, which triggers a full table rewrite and
ACCESS EXCLUSIVE lock on the notification table. Update this migration to
account for the heavy lock impact by either gating the deployment to a
low-traffic maintenance window or adding an explicit pre-check/operational note
based on notification table size before running the change. Reference the
V19__change_notification_type_check.sql migration and its ALTER TABLE
notification block so it’s easy to locate.
🤖 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.

Nitpick comments:
In
`@manabom/src/main/resources/db/migration/V19__change_notification_type_check.sql`:
- Around line 1-17: The V19 notification migration changes notification.type
with ALTER COLUMN TYPE and a USING CASE, which triggers a full table rewrite and
ACCESS EXCLUSIVE lock on the notification table. Update this migration to
account for the heavy lock impact by either gating the deployment to a
low-traffic maintenance window or adding an explicit pre-check/operational note
based on notification table size before running the change. Reference the
V19__change_notification_type_check.sql migration and its ALTER TABLE
notification block so it’s easy to locate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df1f8058-4715-4a9a-934a-e246a5f85240

📥 Commits

Reviewing files that changed from the base of the PR and between b669f43 and a0033f5.

📒 Files selected for processing (2)
  • manabom/src/main/java/mannabom_server/manabom/domain/notification/entity/Notification.java
  • manabom/src/main/resources/db/migration/V19__change_notification_type_check.sql

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

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/application/meeting/service/MeetingVerificationService.java (1)

158-200: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

인증 완료 판정 구간에 락이 없어 최종 위치가 경쟁적으로 덮어써질 수 있음

nearbyCount >= requiredCount 판정부터 FINAL_LOC_KEY 저장까지 별도 동기화 없이 진행됩니다. 여러 사용자가 거의 동시에 마지막 좌표를 제출하면 각자 서로 다른 스냅샷의 bestCluster를 계산해 verify()/reward()를 중복 호출하고, FINAL_LOC_KEY가 서로 다른 좌표로 연쇄적으로 덮어써질 수 있습니다. reward()/verify() 자체는 멱등적이지만, 최종 인증 장소가 뒤늦게 다른 값으로 바뀌면 지각자(processLatecomer)의 거리 검증 기준이 흔들립니다. 같은 파일에서 이미 RedissonClient를 사용 중이니 chatRoomId 기준 분산 락으로 이 임계구간을 보호하는 것을 권장합니다.

RLock lock = redissonClient.getLock("meeting:verify:lock:" + chatRoomId);
lock.lock();
try {
    // 기존 nearbyCount 판정 ~ FINAL_LOC_KEY 저장 로직
} finally {
    lock.unlock();
}
🤖 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 158 - 200, The `processGeneral` flow has a race condition between
the `nearbyCount >= requiredCount` check and writing `FINAL_LOC_KEY`, which can
let concurrent submissions re-run `verify()`/`reward()` and overwrite the final
location. Protect the entire critical section in
`MeetingVerificationService.processGeneral` with a `RedissonClient` lock keyed
by `chatRoomId`, wrapping the cluster evaluation through the final location save
and cleanup in a try/finally. Keep the existing `saveUserLocation`,
`findBestCluster`, `verification.verify()`, `reward()`, and Redis writes inside
that lock so only one request can complete the verification path at a time.
🧹 Nitpick comments (6)
manabom/src/main/java/mannabom_server/manabom/presentation/meeting/controller/MeetingVerificationController.java (2)

20-37: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

요청 바디 검증 누락

@RequestBody MeetingVerificationRequest request@Valid가 없습니다. MeetingVerificationRequest에 검증 애노테이션을 추가한 뒤(별도 파일 코멘트 참고) 이 컨트롤러에도 @Valid를 적용해야 실제로 검증이 동작합니다.

-            `@RequestBody` MeetingVerificationRequest request
+            `@jakarta.validation.Valid` `@RequestBody` MeetingVerificationRequest 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/meeting/controller/MeetingVerificationController.java`
around lines 20 - 37, The MeetingVerificationController#verifyMeeting endpoint
is missing request-body validation, so add `@Valid` to the
MeetingVerificationRequest request parameter and ensure the controller method
still passes latitude/longitude through to
MeetingVerificationService.verifyMeeting; this will activate the validation
annotations defined on MeetingVerificationRequest and make the endpoint reject
invalid payloads as intended.

4-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

서비스 내부 record를 API 응답 타입으로 직접 노출

MeetingVerificationService.MeetingVerificationStatusData(서비스 계층 내부 record)를 그대로 응답 바디로 반환하고 있습니다. 서비스 내부 구현이 바뀌면 API 계약도 함께 깨질 수 있으니, 전용 프레젠테이션 DTO(MeetingVerificationStatusResponse)로 매핑해 반환하는 것을 권장합니다.

Also applies to: 39-46

🤖 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/meeting/controller/MeetingVerificationController.java`
around lines 4 - 6, The controller is exposing the service-layer record
MeetingVerificationService.MeetingVerificationStatusData directly in the API
response. Update MeetingVerificationController to return a presentation DTO such
as MeetingVerificationStatusResponse instead, and map the result from
MeetingVerificationService into that DTO before wrapping it in ApiResponse.
Remove the direct dependency on the internal record so the API contract is owned
by the presentation layer.
manabom/src/main/java/mannabom_server/manabom/presentation/meeting/dto/MeetingVerificationRequest.java (1)

6-9: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

좌표 값 검증 누락

latitude/longitude가 primitive double이라 값이 누락되면 조용히 0.0으로 바인딩되어 잘못된 위치 기준으로 거리 계산이 수행될 수 있습니다. Double로 변경하고 @NotNull 및 위경도 범위 제약을 추가하는 것을 권장합니다(컨트롤러의 @Valid 적용과 함께 동작).

🔧 제안
 package mannabom_server.manabom.presentation.meeting.dto;
 
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotNull;
 import lombok.Getter;
 
 `@Getter`
 public class MeetingVerificationRequest {
-    private double latitude;
-    private double longitude;
+    `@NotNull`
+    `@DecimalMin`("-90.0")
+    `@DecimalMax`("90.0")
+    private Double latitude;
+
+    `@NotNull`
+    `@DecimalMin`("-180.0")
+    `@DecimalMax`("180.0")
+    private Double longitude;
 }
🤖 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/meeting/dto/MeetingVerificationRequest.java`
around lines 6 - 9, MeetingVerificationRequest의 latitude/longitude가 primitive
double이라 누락 시 0.0으로 바인딩되는 문제를 수정하세요. 해당 필드를 Double로 바꾸고,
MeetingVerificationRequest에 `@NotNull과` 위도/경도 범위 검증 제약을 추가해 값이 없거나 범위를 벗어나면 검증
실패하도록 하세요. 이 변경이 동작하려면 요청을 받는 컨트롤러에서 `@Valid가` 적용되는지 함께 확인하세요.
manabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminUserService.java (1)

283-308: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

effectiveStatus, validateSuspension, accountStatusLabel 중복 로직 통합을 권장합니다.

동일한 헬퍼 메서드들이 AdminUserServiceAdminReportService에 중복 정의되어 있으며, accountStatusLabel의 경우 AdminReportService 버전은 null 체크가 누락되어 있어 구현이 이미 분기하고 있습니다. 이 로직을 UserAccountRestriction 엔티티의 정적 메서드로 통합하면 일관성을 유지하고 향후 분기로 인한 버그를 방지할 수 있습니다.

♻️ 제안: 엔티티 정적 메서드로 통합
+ // UserAccountRestriction.java
+ public static UserAccountStatus effectiveStatusOrNull(UserAccountRestriction restriction, LocalDateTime now) {
+     if (restriction == null) {
+         return UserAccountStatus.ACTIVE;
+     }
+     return restriction.effectiveStatus(now);
+ }
+
+ public static String accountStatusLabel(UserAccountRestriction restriction, LocalDateTime now) {
+     if (restriction == null) {
+         return UserAccountStatus.ACTIVE.name();
+     }
+     UserAccountStatus effectiveStatus = restriction.effectiveStatus(now);
+     if (effectiveStatus == UserAccountStatus.SUSPENDED && restriction.getSuspendedUntil() != null) {
+         return effectiveStatus.name() + " until " + restriction.getSuspendedUntil();
+     }
+     return effectiveStatus.name();
+ }
+
+ public static void validateSuspension(UserAccountStatus status, LocalDateTime suspendedUntil) {
+     if (status != UserAccountStatus.SUSPENDED) {
+         return;
+     }
+     if (suspendedUntil == null || !suspendedUntil.isAfter(LocalDateTime.now())) {
+         throw new IllegalArgumentException("정지 만료 시각은 현재 시각 이후여야 합니다.");
+     }
+ }

이후 AdminUserServiceAdminReportService에서는 엔티티 정적 메서드를 호출하도록 변경하면 됩니다.

🤖 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/service/AdminUserService.java`
around lines 283 - 308, `effectiveStatus`, `validateSuspension`, and
`accountStatusLabel` 로직이 `AdminUserService`와 `AdminReportService`에 중복되어 있으니, 이
상태 판정과 라벨 생성/검증을 `UserAccountRestriction` 엔티티의 정적 메서드로 옮겨 공통화하세요.
`UserAccountRestriction`, `effectiveStatus`, `validateSuspension`,
`accountStatusLabel` 같은 식별자를 기준으로 중복 구현을 제거하고, 두 서비스는 엔티티의 공통 메서드를 호출하도록 바꾸어
null 처리와 정지 만료 판단을 한 곳에서 일관되게 유지하세요.
manabom/src/main/resources/static/admin/app.js (1)

1129-1136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

정지 만료 시각 토글 함수 중복.

syncSuspendedUntilInputsyncReportTargetSuspendedUntilInput이 대상 select/label id만 다를 뿐 동일한 로직입니다. 공통 헬퍼로 통합할 수 있습니다.

♻️ 제안 리팩터링
-function syncSuspendedUntilInput() {
-    $("suspendedUntilLabel").classList.toggle("hidden", $("statusSelect").value !== "SUSPENDED");
-}
-
-function syncReportTargetSuspendedUntilInput() {
-    $("reportTargetSuspendedUntilLabel").classList.toggle("hidden", $("reportTargetStatus").value !== "SUSPENDED");
-}
+function syncSuspendedUntilVisibility(selectId, labelId) {
+    $(labelId).classList.toggle("hidden", $(selectId).value !== "SUSPENDED");
+}

호출부는 syncSuspendedUntilVisibility("statusSelect", "suspendedUntilLabel"), syncSuspendedUntilVisibility("reportTargetStatus", "reportTargetSuspendedUntilLabel")로 변경합니다.

🤖 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/static/admin/app.js` around lines 1129 - 1136, The
two toggle helpers are duplicated and differ only by the select/label ids.
Extract the shared logic into a common helper such as
syncSuspendedUntilVisibility that takes the select and label ids, then update
syncSuspendedUntilInput and syncReportTargetSuspendedUntilInput to call it with
statusSelect/suspendedUntilLabel and
reportTargetStatus/reportTargetSuspendedUntilLabel respectively.
manabom/src/main/java/mannabom_server/manabom/presentation/admin/controller/AdminReportController.java (1)

60-66: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

X-Forwarded-For 헤더 무검증 신뢰 - 감사 로그 IP 위조 가능.

신뢰할 수 있는 리버스 프록시 검증 없이 X-Forwarded-For의 첫 값을 그대로 사용합니다. 클라이언트가 이 헤더를 임의로 설정해 감사 로그(ipAddress)에 위조된 값을 남길 수 있어, 관리자 조치 추적성(accountability)이 훼손될 수 있습니다. Spring의 ForwardedHeaderFilter나 신뢰 프록시 목록 기반 검증을 통해 신뢰 경계를 명확히 하는 것을 권장합니다.

🤖 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/admin/controller/AdminReportController.java`
around lines 60 - 66, The clientIp(HttpServletRequest) helper is trusting
X-Forwarded-For directly, which allows forged audit IPs. Update
AdminReportController.clientIp to only use forwarded headers when the request
has passed through a trusted proxy path, or remove this manual parsing and rely
on Spring’s ForwardedHeaderFilter/trusted proxy configuration. Keep the fallback
to request.getRemoteAddr() for untrusted requests, and make the trust boundary
explicit in the controller or surrounding configuration.
🤖 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/service/AdminReportService.java`:
- Around line 136-159: In AdminReportService.processTargetAccount, add a
required validation for request.getTargetAccountReason() before applying
sensitive status changes so the flow matches grantTargetTing’s reason check. Use
the existing request fields and validation style in this service to reject
missing reasons when updating target account status, especially for SUSPENDED or
WITHDRAWN, and keep the audit log call using the validated reason.
- Around line 146-155: The UserAccountRestriction update path in
AdminReportService.processTargetAccount is using findById followed by save,
which can overwrite concurrent admin changes. Update this flow to use a locked
read such as a repository method like findByUserIdForUpdate, or add optimistic
locking with `@Version` on UserAccountRestriction so concurrent updates fail
instead of silently replacing each other. Keep the change aligned with the
existing grantTargetTing locking pattern and preserve the current update(...)
and save(...) flow after the lock is acquired.

In
`@manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java`:
- Around line 253-276: `toBestCluster` is failing hard on missing profiles and
inactive members because it calls `profileRepository.findByUser(...).get()` and
`getActiveChatMember(...)` without a safe fallback. Update
`MeetingVerificationService.toBestCluster` to avoid throwing for users whose
profile is absent or whose chat membership is no longer ACTIVATE: fetch
members/profiles defensively, skip or ignore invalid entries, and compute
`hasMale`/`hasFemale` only from valid profiles so one departed user does not
break cluster calculation.
- Around line 49-68: The first-time creation path in
MeetingVerificationService.verifyMeeting is racy because
MeetingVerificationRepository.findByRoomId(...).orElseGet(save) can let two
concurrent requests attempt to create the same room record. Fix this by making
the single-record creation in verifyMeeting or the repository access atomic,
such as by rechecking after save or using a lock/serialized lookup around the
MeetingVerification.room unique constraint, so only one MeetingVerification is
ever created per ChatRoom and the other request reuses the existing row.

In
`@manabom/src/main/java/mannabom_server/manabom/infrastructure/redis/meetingmatching/RedisMatchAtomicOps.java`:
- Around line 17-19: The fixed 3-second leaseTime in RedisMatchAtomicOps is too
short for the full match flow and can let another worker reacquire the same
meeting lock before processMatchSuccess or rollbackMatch completes. Update the
locking in RedisMatchAtomicOps to use Redisson’s watchdog-backed tryLock
overload or a much longer leaseTime that safely covers candidate search,
Redis/DB work, and chat room creation, and add idempotency protection around the
matching flow so duplicate execution cannot create duplicate matches.

---

Outside diff comments:
In
`@manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java`:
- Around line 158-200: The `processGeneral` flow has a race condition between
the `nearbyCount >= requiredCount` check and writing `FINAL_LOC_KEY`, which can
let concurrent submissions re-run `verify()`/`reward()` and overwrite the final
location. Protect the entire critical section in
`MeetingVerificationService.processGeneral` with a `RedissonClient` lock keyed
by `chatRoomId`, wrapping the cluster evaluation through the final location save
and cleanup in a try/finally. Keep the existing `saveUserLocation`,
`findBestCluster`, `verification.verify()`, `reward()`, and Redis writes inside
that lock so only one request can complete the verification path at a time.

---

Nitpick comments:
In
`@manabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminUserService.java`:
- Around line 283-308: `effectiveStatus`, `validateSuspension`, and
`accountStatusLabel` 로직이 `AdminUserService`와 `AdminReportService`에 중복되어 있으니, 이
상태 판정과 라벨 생성/검증을 `UserAccountRestriction` 엔티티의 정적 메서드로 옮겨 공통화하세요.
`UserAccountRestriction`, `effectiveStatus`, `validateSuspension`,
`accountStatusLabel` 같은 식별자를 기준으로 중복 구현을 제거하고, 두 서비스는 엔티티의 공통 메서드를 호출하도록 바꾸어
null 처리와 정지 만료 판단을 한 곳에서 일관되게 유지하세요.

In
`@manabom/src/main/java/mannabom_server/manabom/presentation/admin/controller/AdminReportController.java`:
- Around line 60-66: The clientIp(HttpServletRequest) helper is trusting
X-Forwarded-For directly, which allows forged audit IPs. Update
AdminReportController.clientIp to only use forwarded headers when the request
has passed through a trusted proxy path, or remove this manual parsing and rely
on Spring’s ForwardedHeaderFilter/trusted proxy configuration. Keep the fallback
to request.getRemoteAddr() for untrusted requests, and make the trust boundary
explicit in the controller or surrounding configuration.

In
`@manabom/src/main/java/mannabom_server/manabom/presentation/meeting/controller/MeetingVerificationController.java`:
- Around line 20-37: The MeetingVerificationController#verifyMeeting endpoint is
missing request-body validation, so add `@Valid` to the MeetingVerificationRequest
request parameter and ensure the controller method still passes
latitude/longitude through to MeetingVerificationService.verifyMeeting; this
will activate the validation annotations defined on MeetingVerificationRequest
and make the endpoint reject invalid payloads as intended.
- Around line 4-6: The controller is exposing the service-layer record
MeetingVerificationService.MeetingVerificationStatusData directly in the API
response. Update MeetingVerificationController to return a presentation DTO such
as MeetingVerificationStatusResponse instead, and map the result from
MeetingVerificationService into that DTO before wrapping it in ApiResponse.
Remove the direct dependency on the internal record so the API contract is owned
by the presentation layer.

In
`@manabom/src/main/java/mannabom_server/manabom/presentation/meeting/dto/MeetingVerificationRequest.java`:
- Around line 6-9: MeetingVerificationRequest의 latitude/longitude가 primitive
double이라 누락 시 0.0으로 바인딩되는 문제를 수정하세요. 해당 필드를 Double로 바꾸고,
MeetingVerificationRequest에 `@NotNull과` 위도/경도 범위 검증 제약을 추가해 값이 없거나 범위를 벗어나면 검증
실패하도록 하세요. 이 변경이 동작하려면 요청을 받는 컨트롤러에서 `@Valid가` 적용되는지 함께 확인하세요.

In `@manabom/src/main/resources/static/admin/app.js`:
- Around line 1129-1136: The two toggle helpers are duplicated and differ only
by the select/label ids. Extract the shared logic into a common helper such as
syncSuspendedUntilVisibility that takes the select and label ids, then update
syncSuspendedUntilInput and syncReportTargetSuspendedUntilInput to call it with
statusSelect/suspendedUntilLabel and
reportTargetStatus/reportTargetSuspendedUntilLabel respectively.
🪄 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

Run ID: 8a6d4c70-1230-42c0-8e21-39127b94e58e

📥 Commits

Reviewing files that changed from the base of the PR and between a0033f5 and 09cceea.

📒 Files selected for processing (34)
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/request/AdminAdjustWalletRequest.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/request/AdminProcessReportRequest.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/request/AdminUpdateUserStatusRequest.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminReportDetailResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminReportListResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminReportSummaryResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminUserDetailResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminUserSummaryResponse.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminAuditService.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminReportService.java
  • manabom/src/main/java/mannabom_server/manabom/application/admin/service/AdminUserService.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/handler/MatchingEventListener.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingMatchingService.java
  • manabom/src/main/java/mannabom_server/manabom/application/meeting/service/MeetingVerificationService.java
  • manabom/src/main/java/mannabom_server/manabom/domain/admin/entity/UserAccountRestriction.java
  • manabom/src/main/java/mannabom_server/manabom/domain/admin/enums/AdminAuditActionType.java
  • manabom/src/main/java/mannabom_server/manabom/domain/admin/enums/AdminAuditTargetType.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingMatch.java
  • manabom/src/main/java/mannabom_server/manabom/domain/meeting/entity/MeetingVerification.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/entity/Report.java
  • manabom/src/main/java/mannabom_server/manabom/domain/report/repository/ReportRepository.java
  • manabom/src/main/java/mannabom_server/manabom/infrastructure/redis/meetingmatching/RedisMatchAtomicOps.java
  • manabom/src/main/java/mannabom_server/manabom/infrastructure/redis/meetingmatching/RedisMatchHistory.java
  • manabom/src/main/java/mannabom_server/manabom/infrastructure/redis/meetingmatching/RedisMatchQueue.java
  • manabom/src/main/java/mannabom_server/manabom/infrastructure/security/admin/UserAccountAccessGuardFilter.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/admin/controller/AdminReportController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/controller/MeetingVerificationController.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/dto/MeetingVerificationRequest.java
  • manabom/src/main/java/mannabom_server/manabom/presentation/meeting/dto/MeetingVerificationResponse.java
  • manabom/src/main/resources/db/migration/V18__add_admin_report_cs_support.sql
  • manabom/src/main/resources/db/migration/V20__add_meeting_verification_valid_window.sql
  • manabom/src/main/resources/static/admin/app.js
  • manabom/src/main/resources/static/admin/index.html
  • manabom/src/main/resources/static/admin/styles.css
✅ Files skipped from review due to trivial changes (2)
  • manabom/src/main/resources/db/migration/V20__add_meeting_verification_valid_window.sql
  • manabom/src/main/java/mannabom_server/manabom/application/admin/dto/response/AdminReportSummaryResponse.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants