-
Notifications
You must be signed in to change notification settings - Fork 0
Feature / admin / app #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ing and pagination
…sary parameters and enhance member retrieval
Walkthrough관리자용 Redot 앱 관리 API가 추가되었습니다. AdminRedotAppController에 앱 목록 조회(검색·페이징), 단일 조회, 상태(및 remark) 업데이트 엔드포인트가 추가되었고, RedotApp 엔티티에 Sequence Diagram(s)sequenceDiagram
participant Admin as 관리자 클라이언트
participant Controller as AdminRedotAppController
participant Service as AdminRedotAppService
participant Repo as RedotAppRepository / 기타Repo
participant DB as Database
rect rgb(200,220,240)
Note over Admin,DB: 앱 목록 조회 (검색 · 페이징 · 정렬)
Admin->>Controller: GET /api/v1/redot/admin/app\n(searchCondition, pageable)
Controller->>Service: getRedotAppInfoList(searchCondition, pageable)
Service->>Repo: findAllWithSiteInfo / findAllBySearchCondition(searchCondition, pageable)
Repo->>DB: QueryDSL 동적 쿼리 (name, ownerId, status, joins, order, limit/offset)
DB-->>Repo: 결과(Row/Tuple)
Repo-->>Service: Page<RedotApp> / List<RedotAppWithSiteInfo>
Service->>Service: 도메인/스타일/사이트 매핑 및 toRedotAppInfoResponse 변환
Service-->>Controller: PageResponse<RedotAppInfoResponse>
Controller-->>Admin: 200 OK + 페이징 응답
end
rect rgb(240,200,220)
Note over Admin,DB: 앱 상태 업데이트 (remark 포함)
Admin->>Controller: POST /api/v1/redot/admin/app/{id}/status\n(status, remark)
Controller->>Service: updateRedotAppStatus(appId, request)
Service->>Repo: findById(appId)
Repo->>DB: SELECT * FROM redot_apps WHERE id=...
DB-->>Repo: RedotApp
Repo-->>Service: RedotApp
Service->>Service: redotApp.updateStatus(status, remark)
Service->>Repo: save(redotApp)
Repo->>DB: UPDATE redot_apps SET status=..., remark=...
DB-->>Repo: 업데이트 완료
Service->>Service: toRedotAppInfoResponse(redotApp)
Service-->>Controller: RedotAppInfoResponse
Controller-->>Admin: 200 OK + 업데이트된 앱 정보
end
rect rgb(220,240,200)
Note over Admin,DB: 임퍼소네이션 변경 — OWNER 조회로 토큰 발급
Admin->>Controller: POST /api/v1/auth/cms/impersonate\n(CMSAdminImpersonationRequest)
Controller->>Service: impersonateAsCMSAdmin(request)
Service->>Repo: findFirstByRedotApp_IdAndRoleOrderByIdAsc(appId, OWNER)
Repo->>DB: SELECT ... FROM cms_members WHERE redot_app_id=? AND role='OWNER' ORDER BY id ASC LIMIT 1
DB-->>Repo: Optional<CMSMember>
Repo-->>Service: CMSMember(OWNER) or empty -> 예외
Service->>Service: TokenContext(owner.id, owner.role)
Service-->>Controller: AuthResult(tokenResponse)
Controller-->>Admin: 200 OK + TokenResponse (body) + 쿠키/헤더
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45분
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/main/java/redot/redot_server/domain/redot/app/entity/RedotApp.java (1)
85-88:statusnull 허용 여부 확인 필요.
status필드는@Column(nullable = false)로 선언되어 있지만,updateStatus메서드에서 null 체크 없이 직접 할당합니다. 호출부(DTO)에서@NotNull검증이 있지만, 방어적 코딩 관점에서 메서드 내부에서도 검증을 고려해 볼 수 있습니다.public void updateStatus(RedotAppStatus status, String remark) { + if (status == null) { + throw new IllegalArgumentException("status cannot be null"); + } this.status = status; this.remark = remark; }src/main/java/redot/redot_server/domain/admin/controller/AdminRedotAppController.java (1)
52-58: REST 규약에 맞게@PutMapping또는@PatchMapping사용 권장상태 변경은 리소스 수정 작업이므로
@PostMapping대신@PutMapping또는@PatchMapping이 더 적절합니다. PR 목표에서도 "PUT endpoint to modify app information"을 명시하고 있으며,AdminConsultationController에서도 수정 작업에@PutMapping을 사용합니다.- @PostMapping("/{redotAppId}/status") + @PutMapping("/{redotAppId}/status") @Override public ResponseEntity<RedotAppInfoResponse> updateRedotAppStatus( @PathVariable("redotAppId") Long redotAppId, @Valid @RequestBody RedotAppStatusUpdateRequest request) { return ResponseEntity.ok(redotAppService.updateRedotAppStatus(redotAppId, request)); }
PutMappingimport 추가도 필요합니다:import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping;
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
src/main/java/redot/redot_server/domain/admin/controller/AdminRedotAppController.java(2 hunks)src/main/java/redot/redot_server/domain/admin/controller/docs/AdminRedotAppControllerDocs.java(2 hunks)src/main/java/redot/redot_server/domain/admin/dto/request/RedotAppInfoSearchCondition.java(1 hunks)src/main/java/redot/redot_server/domain/admin/dto/request/RedotAppStatusUpdateRequest.java(1 hunks)src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java(1 hunks)src/main/java/redot/redot_server/domain/auth/controller/AdminImpersonationController.java(1 hunks)src/main/java/redot/redot_server/domain/auth/controller/docs/AdminImpersonationControllerDocs.java(1 hunks)src/main/java/redot/redot_server/domain/auth/service/AdminImpersonationService.java(2 hunks)src/main/java/redot/redot_server/domain/cms/member/repository/CMSMemberRepository.java(2 hunks)src/main/java/redot/redot_server/domain/redot/app/dto/response/RedotAppResponse.java(2 hunks)src/main/java/redot/redot_server/domain/redot/app/entity/RedotApp.java(2 hunks)src/main/java/redot/redot_server/domain/redot/app/entity/RedotAppStatus.java(1 hunks)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepository.java(1 hunks)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryCustom.java(1 hunks)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java(1 hunks)src/main/resources/db/migration/V6__add_redot_app_remark.sql(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java (5)
src/main/java/redot/redot_server/domain/redot/app/exception/RedotAppException.java (1)
RedotAppException(5-9)src/main/java/redot/redot_server/domain/site/domain/exception/DomainException.java (1)
DomainException(5-9)src/main/java/redot/redot_server/domain/site/setting/exception/SiteSettingException.java (1)
SiteSettingException(5-9)src/main/java/redot/redot_server/domain/site/style/exception/StyleInfoException.java (1)
StyleInfoException(5-9)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java (1)
RequiredArgsConstructor(18-79)
src/main/java/redot/redot_server/domain/admin/controller/AdminRedotAppController.java (2)
src/main/java/redot/redot_server/domain/admin/controller/AdminConsultationController.java (1)
RestController(22-56)src/main/java/redot/redot_server/domain/redot/app/controller/RedotAppController.java (1)
RestController(26-63)
src/main/java/redot/redot_server/domain/auth/service/AdminImpersonationService.java (1)
src/main/java/redot/redot_server/domain/auth/exception/AuthException.java (1)
AuthException(5-13)
🔇 Additional comments (21)
src/main/java/redot/redot_server/domain/redot/app/entity/RedotAppStatus.java (1)
8-9: LGTM!
BANNED상태 추가가 잘 되어 있습니다.PAYMENT_DELAYED에 trailing comma를 추가한 것도 향후 확장성을 고려한 좋은 패턴입니다.src/main/resources/db/migration/V6__add_redot_app_remark.sql (1)
1-2: LGTM!
IF NOT EXISTS사용으로 멱등성이 보장되고,TEXT타입이 엔티티의columnDefinition = "text"와 일치합니다. nullable 컬럼으로 선택적 remark를 적절히 지원합니다.src/main/java/redot/redot_server/domain/admin/dto/request/RedotAppInfoSearchCondition.java (1)
5-9: LGTM!검색 조건 DTO로 record를 사용한 것이 적절합니다. 모든 필드가 nullable로 선택적 필터링을 올바르게 지원합니다.
src/main/java/redot/redot_server/domain/redot/app/entity/RedotApp.java (1)
44-46: LGTM!
remark필드 추가가 마이그레이션과 일치하며, nullable 컬럼으로 적절히 정의되어 있습니다.src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryCustom.java (1)
8-11: LGTM!Spring Data 커스텀 리포지토리 패턴을 올바르게 따르고 있으며, 페이지네이션과 검색 조건을 지원하는 메서드 시그니처가 적절합니다.
src/main/java/redot/redot_server/domain/admin/dto/request/RedotAppStatusUpdateRequest.java (1)
6-9: LGTM!
@NotNull검증으로 status 필수값을 보장하고, remark는 선택적으로 적절히 설계되었습니다.src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepository.java (1)
8-8: LGTM!
RedotAppRepositoryCustom확장으로 커스텀 쿼리 기능이 올바르게 통합되었습니다. Spring Data가RedotAppRepositoryImpl을 자동으로 연결합니다.src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java (3)
23-53: LGTM!QueryDSL을 활용한 동적 쿼리 구현이 잘 되어 있습니다. null-safe한 검색 조건 처리와 페이지네이션이 적절합니다.
55-62: 정렬 필드가createdAt으로 제한됨.현재
resolveOrder는createdAt필드만 처리하고, 다른 정렬 필드 요청은 무시하고 기본값(createdAt DESC)으로 대체됩니다. 의도된 동작이라면 괜찮지만, 향후 다른 필드(name, status 등) 정렬이 필요할 경우 확장이 필요합니다.
64-78: LGTM!predicate 헬퍼 메서드들이 깔끔하게 구현되어 있습니다.
containsIgnoreCase로 대소문자 무관 검색을 지원하고,hasText유틸리티로 빈 문자열 처리가 잘 되어 있습니다.src/main/java/redot/redot_server/domain/admin/controller/docs/AdminRedotAppControllerDocs.java (1)
26-44: API 문서가 잘 작성되었습니다.새로운 엔드포인트 3개(목록 조회, 단건 조회, 상태 변경)에 대한 Swagger 문서가 적절하게 정의되어 있습니다.
@ParameterObject와@io.swagger.v3.oas.annotations.Parameter어노테이션이 올바르게 적용되었습니다.src/main/java/redot/redot_server/domain/admin/controller/AdminRedotAppController.java (1)
37-43: 목록 조회 엔드포인트 구현이 적절합니다.페이지네이션과 검색 조건이
@ParameterObject로 올바르게 바인딩되어 있으며, 기본 정렬이createdAt DESC로 설정되어 있습니다.AdminConsultationController의 패턴과 일관성 있게 구현되었습니다.src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java (3)
52-59: 페이지네이션 및 목록 조회 로직이 잘 구현되었습니다.
findAllBySearchCondition을 통해 검색 조건과 페이지네이션을 적용한 후PageResponse로 변환하는 흐름이 적절합니다.
79-84: 단건 조회 로직이 적절합니다.앱 ID로 조회 후 not found 예외 처리가 올바르게 구현되어 있습니다. 단건 조회에서는 관련 엔티티가 없을 때 예외를 던지는 것이 적절합니다.
86-94: 상태 업데이트 로직이 올바르게 구현되었습니다.
@Transactional이 적용되어 있고, 엔티티 조회 후updateStatus메서드를 통해 상태와 remark를 업데이트합니다. Dirty checking을 통해 자동으로 persist됩니다.src/main/java/redot/redot_server/domain/redot/app/dto/response/RedotAppResponse.java (1)
16-25: NPE 우려는 타당하지 않습니다. RedotApp 엔티티의 plan 필드는@JoinColumn(name = "plan_id", nullable = false)로 정의되어 있어 데이터베이스 수준에서 NOT NULL 제약이 적용됩니다. 따라서redotApp.getPlan().getId()호출 시 NPE가 발생할 수 없습니다.Likely an incorrect or invalid review comment.
src/main/java/redot/redot_server/domain/auth/controller/docs/AdminImpersonationControllerDocs.java (1)
21-22: 변경사항이 적절합니다.컨트롤러의 시그니처 변경과 일치하도록 문서 인터페이스가 올바르게 업데이트되었습니다.
src/main/java/redot/redot_server/domain/auth/controller/AdminImpersonationController.java (1)
33-33: 응답 본문 추가는 적절합니다.쿠키와 함께 응답 본문에도 토큰 정보를 포함하도록 개선되어, 클라이언트가 쿠키와 본문 중 선택하여 사용할 수 있습니다.
src/main/java/redot/redot_server/domain/auth/service/AdminImpersonationService.java (2)
34-35: 토큰 컨텍스트가 OWNER 권한으로 올바르게 설정되었습니다.이전의 하드코딩된
CMSMemberRole.ADMIN대신 실제 OWNER의 역할(owner.getRole().name())을 사용하여 토큰을 발급하도록 개선되었습니다. 이는 CMS 관리자 권한이 실제 OWNER 권한과 일치하도록 하려는 PR 목표와 부합합니다.
28-31: CMS OWNER 계정으로 토큰 발급 로직이 올바르게 구현되었습니다.
AuthErrorCode.CMS_MEMBER_NOT_FOUND가 존재하며 적절한 메시지를 포함합니다. RedotApp 생성 시createManager()메서드에서 OWNER 역할의 CMSMember가 반드시 생성되므로, 애플리케이션 수준에서 OWNER 존재를 보장합니다. 저장소 쿼리에서findFirstByRedotApp_IdAndRoleOrderByIdAsc를 사용하므로, 여러 OWNER가 있는 경우에도 안전하게 첫 번째를 반환합니다.src/main/java/redot/redot_server/domain/cms/member/repository/CMSMemberRepository.java (1)
25-25: 고유 제약조건이 OWNER 단일성을 보장합니다.데이터베이스 마이그레이션 파일에서 부분 고유 인덱스
ux_cms_members_owner_per_redot_app(라인 168-170)이 정의되어 있어,(redot_app_id)조합에서role = 'OWNER' AND status = 'ACTIVE'조건 하에서 정확히 하나의 OWNER만 존재할 수 있도록 강제합니다. 따라서findFirstByRedotApp_IdAndRoleOrderByIdAsc메서드는 최대 하나의 결과를 반환하도록 보장되며, Optional로 반환하는 것과 함께 이 메서드의 사용은 적절합니다.
src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java
Show resolved
Hide resolved
src/main/java/redot/redot_server/domain/auth/controller/AdminImpersonationController.java
Show resolved
Hide resolved
…ctoring response mapping
There was a problem hiding this 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)
src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java (1)
90-103: Predicate 생성 로직 중복 개선 고려.
nameContains,ownerIdEq,statusEqpredicate 배열 생성 로직이findAllBySearchCondition,findAllWithSiteInfo,countBySearchCondition세 메서드에서 반복됩니다.Private 헬퍼 메서드로 추출하면 유지보수성이 향상됩니다:
+ private BooleanExpression[] buildPredicates(RedotAppInfoSearchCondition searchCondition) { + String name = searchCondition == null ? null : searchCondition.name(); + Long ownerId = searchCondition == null ? null : searchCondition.redotMemberId(); + RedotAppStatus status = searchCondition == null ? null : searchCondition.status(); + + return new BooleanExpression[]{ + nameContains(name), + ownerIdEq(ownerId), + statusEq(status) + }; + }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java(1 hunks)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryCustom.java(1 hunks)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java(1 hunks)src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppWithSiteInfo.java(1 hunks)src/main/java/redot/redot_server/domain/site/domain/repository/DomainRepository.java(2 hunks)src/main/java/redot/redot_server/domain/site/setting/repository/SiteSettingRepository.java(1 hunks)src/main/java/redot/redot_server/domain/site/style/repository/StyleInfoRepository.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryCustom.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java (2)
src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java (1)
RequiredArgsConstructor(23-139)src/main/java/redot/redot_server/domain/redot/app/exception/RedotAppException.java (1)
RedotAppException(5-9)
🔇 Additional comments (12)
src/main/java/redot/redot_server/domain/site/setting/repository/SiteSettingRepository.java (1)
11-12: LGTM!Spring Data JPA 네이밍 규칙을 올바르게 따르는 배치 조회 메서드입니다.
In접미사와Collection<Long>파라미터 조합이 적절합니다.src/main/java/redot/redot_server/domain/site/style/repository/StyleInfoRepository.java (1)
11-12: LGTM!기존
findByRedotApp_Id메서드와 일관된 네이밍 규칙을 사용하며, 중첩 속성 탐색을 위한 언더스코어 표기법이 올바르게 적용되었습니다.src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppWithSiteInfo.java (1)
8-14: LGTM!쿼리 결과를 집계하기 위한 불변 레코드로 적절합니다. Left join 사용으로 인해
domain,siteSetting,styleInfo가 nullable일 수 있다는 점이 설계에 반영되어 있습니다.src/main/java/redot/redot_server/domain/site/domain/repository/DomainRepository.java (1)
20-21: LGTM!기존
findByRedotAppId메서드와 일관되게@EntityGraph없이 구현되었습니다. 배치 조회를 위한 표준 Spring Data JPA 패턴입니다.src/main/java/redot/redot_server/domain/redot/app/repository/RedotAppRepositoryImpl.java (3)
28-52: LGTM!QueryDSL을 활용한 동적 쿼리 구현이 깔끔합니다. Null-safe 파라미터 처리와 페이지네이션 로직이 적절합니다.
54-88: LGTM!Left join을 통해 관련 엔티티들을 효율적으로 조회하고 있습니다.
filter(appWithInfo -> appWithInfo.redotApp() != null)처리로 null 결과를 안전하게 필터링합니다.
115-122: 정렬 옵션이createdAt으로 제한됨.현재 구현은
createdAt외의 정렬 속성을 조용히 무시합니다. PR 목표에 따르면createdAt정렬만 필요하므로 현재 구현은 적절하지만, 향후 확장 시 다른 정렬 필드 지원이 필요할 수 있습니다.src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java (5)
50-82: 이전 리뷰의 N+1 쿼리 문제가 해결되었습니다!목록 조회 시 관련 엔티티(Domain, StyleInfo, SiteSetting)를 IN 쿼리로 일괄 조회하여 Map으로 구성하는 방식으로 변경되어, 이전 리뷰에서 지적된 N+1 쿼리 문제가 효과적으로 해결되었습니다. 또한 빈 목록에 대한 가드 조건도 적절히 처리되어 있습니다.
84-110: 이전 리뷰의 예외 처리 전략이 개선되었습니다!관련 엔티티가 없을 때 예외를 던지지 않고 null을 반환하도록 변경되어, 이전 리뷰에서 지적된 부분 데이터 표시 문제가 해결되었습니다. nullable 파라미터를 받는 오버로드 메서드가 null 체크를 통해 안전하게 응답을 구성합니다.
단일 엔티티 조회 시 개별 쿼리 실행은 허용 가능한 수준입니다.
112-117: LGTM!단일 앱 조회 로직이 적절하며, 엔티티가 없을 때 예외를 던지는 것이 타당합니다.
119-127: LGTM!상태 업데이트 로직이 적절하며,
@Transactional어노테이션이 올바르게 적용되어 있습니다. 엔티티의updateStatus메서드를 호출하여 도메인 로직을 캡슐화한 것도 좋은 설계입니다.
59-71: 배치 조회 메서드 확인 완료요청한 메서드들이 모두 정의되어 있습니다:
DomainRepository.findByRedotAppIdIn(Line 20)StyleInfoRepository.findByRedotApp_IdIn(Line 12)SiteSettingRepository.findByRedotAppIdIn(Line 12)메서드 시그니처와 사용 방식이 올바르게 일치하므로 특별한 조치가 필요하지 않습니다.
| Map<Long, Domain> domainMap = redotAppIds.isEmpty() ? Map.of() | ||
| : domainRepository.findByRedotAppIdIn(redotAppIds) | ||
| .stream() | ||
| .collect(Collectors.toMap(domain -> domain.getRedotApp().getId(), Function.identity())); | ||
|
|
||
| Map<Long, StyleInfo> styleInfoMap = redotAppIds.isEmpty() ? Map.of() | ||
| : styleInfoRepository.findByRedotApp_IdIn(redotAppIds) | ||
| .stream() | ||
| .collect(Collectors.toMap(style -> style.getRedotApp().getId(), Function.identity())); | ||
|
|
||
| Map<Long, SiteSetting> siteSettingMap = redotAppIds.isEmpty() ? Map.of() | ||
| : siteSettingRepository.findByRedotAppIdIn(redotAppIds) | ||
| .stream() | ||
| .collect(Collectors.toMap(setting -> setting.getRedotApp().getId(), Function.identity())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중복 키 발생 시 예외 처리 필요
Collectors.toMap에 merge function이 없어 하나의 RedotApp에 여러 Domain/StyleInfo/SiteSetting이 존재할 경우 IllegalStateException이 발생합니다. 또한 getRedotApp()이 null을 반환하면 NPE가 발생할 수 있습니다.
다음 diff를 적용하여 중복 키와 null 처리를 개선하세요:
Map<Long, Domain> domainMap = redotAppIds.isEmpty() ? Map.of()
: domainRepository.findByRedotAppIdIn(redotAppIds)
.stream()
+ .filter(domain -> domain.getRedotApp() != null)
- .collect(Collectors.toMap(domain -> domain.getRedotApp().getId(), Function.identity()));
+ .collect(Collectors.toMap(
+ domain -> domain.getRedotApp().getId(),
+ Function.identity(),
+ (existing, replacement) -> existing));
Map<Long, StyleInfo> styleInfoMap = redotAppIds.isEmpty() ? Map.of()
: styleInfoRepository.findByRedotApp_IdIn(redotAppIds)
.stream()
+ .filter(style -> style.getRedotApp() != null)
- .collect(Collectors.toMap(style -> style.getRedotApp().getId(), Function.identity()));
+ .collect(Collectors.toMap(
+ style -> style.getRedotApp().getId(),
+ Function.identity(),
+ (existing, replacement) -> existing));
Map<Long, SiteSetting> siteSettingMap = redotAppIds.isEmpty() ? Map.of()
: siteSettingRepository.findByRedotAppIdIn(redotAppIds)
.stream()
+ .filter(setting -> setting.getRedotApp() != null)
- .collect(Collectors.toMap(setting -> setting.getRedotApp().getId(), Function.identity()));
+ .collect(Collectors.toMap(
+ setting -> setting.getRedotApp().getId(),
+ Function.identity(),
+ (existing, replacement) -> existing));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Map<Long, Domain> domainMap = redotAppIds.isEmpty() ? Map.of() | |
| : domainRepository.findByRedotAppIdIn(redotAppIds) | |
| .stream() | |
| .collect(Collectors.toMap(domain -> domain.getRedotApp().getId(), Function.identity())); | |
| Map<Long, StyleInfo> styleInfoMap = redotAppIds.isEmpty() ? Map.of() | |
| : styleInfoRepository.findByRedotApp_IdIn(redotAppIds) | |
| .stream() | |
| .collect(Collectors.toMap(style -> style.getRedotApp().getId(), Function.identity())); | |
| Map<Long, SiteSetting> siteSettingMap = redotAppIds.isEmpty() ? Map.of() | |
| : siteSettingRepository.findByRedotAppIdIn(redotAppIds) | |
| .stream() | |
| .collect(Collectors.toMap(setting -> setting.getRedotApp().getId(), Function.identity())); | |
| Map<Long, Domain> domainMap = redotAppIds.isEmpty() ? Map.of() | |
| : domainRepository.findByRedotAppIdIn(redotAppIds) | |
| .stream() | |
| .filter(domain -> domain.getRedotApp() != null) | |
| .collect(Collectors.toMap( | |
| domain -> domain.getRedotApp().getId(), | |
| Function.identity(), | |
| (existing, replacement) -> existing)); | |
| Map<Long, StyleInfo> styleInfoMap = redotAppIds.isEmpty() ? Map.of() | |
| : styleInfoRepository.findByRedotApp_IdIn(redotAppIds) | |
| .stream() | |
| .filter(style -> style.getRedotApp() != null) | |
| .collect(Collectors.toMap( | |
| style -> style.getRedotApp().getId(), | |
| Function.identity(), | |
| (existing, replacement) -> existing)); | |
| Map<Long, SiteSetting> siteSettingMap = redotAppIds.isEmpty() ? Map.of() | |
| : siteSettingRepository.findByRedotAppIdIn(redotAppIds) | |
| .stream() | |
| .filter(setting -> setting.getRedotApp() != null) | |
| .collect(Collectors.toMap( | |
| setting -> setting.getRedotApp().getId(), | |
| Function.identity(), | |
| (existing, replacement) -> existing)); |
🤖 Prompt for AI Agents
In
src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java
around lines 58 to 71, the three Collectors.toMap calls can throw
IllegalStateException on duplicate keys and NPE if getRedotApp() returns null;
update each stream to filter out entries with null getRedotApp() (or null
getRedotApp().getId()) before collecting and supply a merge function to
Collectors.toMap to resolve duplicate keys (for example, choose the first or
last occurrence consistently), ensuring no NPE and no duplicate-key exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한개의 앱에는 하나의 domain, styleinfo, sitesetting만 존재해요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eraser502 이해했습니다! 1:1 관계라면 중복 키 문제는 발생하지 않겠네요. 명확하게 설명해 주셔서 감사합니다. 😊
✏️ Learnings added
Learnt from: eraser502
Repo: redotlabs/server PR: 134
File: src/main/java/redot/redot_server/domain/admin/service/AdminRedotAppService.java:58-71
Timestamp: 2025-12-17T01:39:43.440Z
Learning: In the RedotApp domain model, there is a 1:1 relationship between RedotApp and Domain, StyleInfo, and SiteSetting entities. Each RedotApp has exactly one Domain, one StyleInfo, and one SiteSetting.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Overview
Related Issue
PR Checklist
Additional Information