Skip to content

Conversation

@sweatbuckets
Copy link
Contributor

@sweatbuckets sweatbuckets commented Jan 5, 2026

  1. #⃣ 연관된 이슈
    • 관련 이슈를 명시해주세요.
    • 예: #이슈번호#이슈번호
  2. 📝 작업 내용
    • 사진첩과 연결할 공연 조회 api 개발
    • 일반유저 접근 페이지 수정
    • specification 이용한 approval 조건 추가
    • 공연 조회 api 쿼리 성능 최적화
  3. 📸 스크린샷 (선택)
    • 작업 내용을 시각적으로 표현할 스크린샷을 포함하세요.
  4. 💬 리뷰 요구사항 (선택)
    • 리뷰어가 특히 검토해주었으면 하는 부분이 있다면 작성해주세요.
    • 예: "메서드 XXX의 이름을 더 명확히 하고 싶은데, 좋은 아이디어가 있으신가요?"

Summary by CodeRabbit

  • New Features

    • Added "My Shows" endpoint for performers to list their shows for photo albums.
  • Improvements

    • Several show listing endpoints (ranking, today, ongoing, closing, recently hot, incoming) are now public — no login required.
    • Photo album browsing is now public (visitor-first cursor paging).
    • Show filtering improved to enforce approval status and support date-based round filtering.
  • Security

    • Photo album deletion now restricted to performers.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

Refactors amateur-show and photo-album APIs to remove member-context from multiple endpoints, replaces in-memory filtering with Specification-driven repository queries (including date-round filters), and adds a performer-only photo-album endpoint to list a member's shows.

Changes

Cohort / File(s) Summary
Amateur Show Controller
src/main/java/cc/backend/amateurShow/controller/AmateurController.java
Removed MemberService field and stripped member parameters from several endpoints (getAmateurShow, getShowRanking, getShowToday, getShowOngoing, getShowClosing, getRecentlyHotShow, getShowIncoming); methods now delegate to service without member context.
Amateur Show Repository
src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java
Added findAllByMemberId(Long); removed older member-ordered and end-date methods; tightened findHotShows query to require approvalStatus = 'APPROVED'.
Amateur Show Specification
src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java
Replaced isAudienceVisible() with isApproved(); added hasRoundOn(LocalDate) (join-based) and hasLastRoundOn(LocalDate) (subquery for greatest round date); added JPA Criteria imports for join/subquery logic.
Amateur Show Service (interface & impl)
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurService.java
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
Removed memberId from multiple service methods (getAmateurShow, getShowToday, getShowOngoing, getShowRanking, getShowClosing, getRecentlyHotShow); switched to Specification-based repository queries, centralized paging/sorting, and mapped repository results to DTOs.
Photo Album Controller
src/main/java/cc/backend/photoAlbum/controller/PhotoAlbumController.java
Added getMyShows endpoint (requires PERFORMER role); removed member parameter from getAllPhotoAlbum and now calls service cursor-based method without member context.
Photo Album DTO
src/main/java/cc/backend/photoAlbum/dto/PhotoAlbumResponseDTO.java
Added MyShowsForPhotoAlbumDTO inner class with memberId, amateurShowId, and schedule fields.
Photo Album Service (interface & impl)
src/main/java/cc/backend/photoAlbum/service/PhotoAlbumService.java
src/main/java/cc/backend/photoAlbum/service/PhotoAlbumServiceImpl.java
Removed memberId from getAllRecentPhotoAlbumList; added getMyShows(Long memberId) with performer-role validation and schedule formatting; removed S3Service field usage and added mergeScheduleWithoutDays helper.

Sequence Diagram(s)

sequenceDiagram
    %% Styling hint: controller/service/repo flow for show listing
    participant Client as Client
    participant Ctrl as AmateurController
    participant Svc as AmateurServiceImpl
    participant Repo as AmateurShowRepository
    rect rgb(240,248,255)
      Note right of Ctrl: Request without member context
    end
    Client->>Ctrl: GET /amateurShows/today?page...
    Ctrl->>Svc: getShowToday(pageable)
    Svc->>Svc: build Specification (isApproved ∧ hasRoundOn(today))
    Svc->>Repo: findAll(spec, pageable)
    Repo-->>Svc: Slice<AmateurShow>
    Svc->>Svc: map -> DTO (mergeSchedule)
    Svc-->>Ctrl: Slice<DTO>
    Ctrl-->>Client: 200 OK (paged DTOs)
Loading
sequenceDiagram
    %% PhotoAlbum: performer-only getMyShows flow
    participant Client as Client
    participant Ctrl as PhotoAlbumController
    participant Svc as PhotoAlbumServiceImpl
    participant Repo as AmateurShowRepository
    Client->>Ctrl: GET /photoAlbums/getMyShows (authenticated PERFORMER)
    Ctrl->>Svc: getMyShows(memberId)
    Svc->>Svc: verify role == PERFORMER
    Svc->>Repo: findAllByMemberId(memberId)
    Repo-->>Svc: List<AmateurShow>
    Svc->>Svc: map -> MyShowsForPhotoAlbumDTO (mergeScheduleWithoutDays)
    Svc-->>Ctrl: List<MyShowsForPhotoAlbumDTO>
    Ctrl-->>Client: 200 OK (list)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped through specs and joined each round,

Dropped member crumbs from endpoints found.
Albums now list my shows (performer proud),
DB filters sing and responses crowd.
A little hop—API tidy and sound 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.81% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: developing a show lookup API for photo albums, modifying general user access pages, and adding approval conditions using specifications.
Description check ✅ Passed The description addresses the required template structure with work content clearly listed, but the related issues section remains unfilled with only template placeholder text.
✨ Finishing touches
  • 📝 Generate docstrings

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

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

Fix all issues with AI Agents 🤖
In @src/main/java/cc/backend/amateurShow/controller/AmateurController.java:
- Around line 95-100: The getShowIncoming controller method currently calls
amateurService.getShowToday, which mismatches the operation summary for "임박한 공연
조회 API"; update AmateurController.getShowIncoming to call the correct service
method (e.g., amateurService.getShowIncoming or amateurService.getUpcomingShows)
that returns upcoming/imminent shows instead of today's shows — if that service
method does not exist, implement a new method in AmateurService (and its impl)
that queries for upcoming rounds and returns a Slice<AmateurShowList>, and
ensure the returned DTO/signature matches SliceResponse.of(...) used in the
controller.

In
@src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java:
- Around line 561-567: Using Integer.MAX_VALUE as the page size in
PageRequest.of risks loading an unbounded number of AmateurShow entities into
memory; replace this with a reasonable upper bound (e.g., a defined constant
MAX_PAGE_SIZE or a sensible cap like 1000) or switch to a streaming/batching
approach using repository methods that return a Stream or Pageable-backed
iteration; update the PageRequest.of call (currently constructing sortedPage)
and/or change the call path via amateurShowRepository.findAll(spec, ...) to use
streaming (e.g., a repository method returning Stream<AmateurShow>) or looped
Pageable requests to fetch results in chunks.

In @src/main/java/cc/backend/photoAlbum/service/PhotoAlbumServiceImpl.java:
- Around line 374-376: The if-check in PhotoAlbumServiceImpl that throws
GeneralException when member.getRole() != Role.PERFORMER is redundant because
the controller already enforces @PreAuthorize("hasRole('PERFORMER')"); remove
the role check (the if block referencing member.getRole() and Role.PERFORMER)
from PhotoAlbumServiceImpl so the service relies on controller/security
annotations (PhotoAlbumController's @PreAuthorize) and leave any necessary
null/ownership checks only.

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java (1)

602-613: status may be null when passed to DTO builder.

When status == null, the query retrieves all shows regardless of status (line 596), but the DTO builder on line 611 sets status(status) which will be null. This could confuse API consumers expecting the actual status of each show.

Consider using the show's actual status instead:

🔎 Proposed fix
         return shows.map(show -> {
             String schedule = AmateurConverter.mergeSchedule(show.getStart(), show.getEnd());
             return AmateurShowResponseDTO.MyShowAmateurShowList.builder()
                     .amateurShowId(show.getId())
                     .name(show.getName())
                     //.place(show.getPlace())
                     .detailAddress(show.getDetailAddress())
                     .schedule(schedule)
                     .posterImageUrl(show.getPosterImageUrl())
-                    .status(status)
+                    .status(show.getStatus())
                     .build();
         });
🧹 Nitpick comments (3)
src/main/java/cc/backend/photoAlbum/dto/PhotoAlbumResponseDTO.java (1)

76-84: Consider removing the redundant memberId field.

The memberId field in MyShowsForPhotoAlbumDTO appears redundant since the service method getMyShows always sets it to the requesting user's ID (the same memberId parameter passed to the method). The client already knows their own ID, and including it in every item of the returned list adds unnecessary data transfer overhead.

🔎 Proposed refactor to remove memberId field
     @Builder
     @Getter
     @NoArgsConstructor
     @AllArgsConstructor
     public static class MyShowsForPhotoAlbumDTO {
-        private Long memberId;
         private Long amateurShowId;
         private String schedule;
     }

Then update the service implementation accordingly:

         return amateurShows.stream()
                 .map(show->{
                     String schedule = mergeScheduleWithoutDays(show.getStart(), show.getEnd());
                     return PhotoAlbumResponseDTO.MyShowsForPhotoAlbumDTO.builder()
                             .amateurShowId(show.getId())
-                            .memberId(memberId)
                             .schedule(schedule)
                             .build();
                 })
                 .collect(Collectors.toList());
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurService.java (1)

30-30: Remove commented-out code.

Commented-out code should be removed to keep the codebase clean. If the functionality is needed later, it can be retrieved from version control.

🔎 Proposed fix
-   // ReserveListResponseDTO getReserveListDetail(Long amateurShowId, Long memberId);
src/main/java/cc/backend/amateurShow/controller/AmateurController.java (1)

11-11: Unused import.

MemberService is imported but no longer used after removing the member-related field.

🔎 Proposed fix
-import cc.backend.member.MemberService;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c24039 and d5f4bdf.

📒 Files selected for processing (9)
  • src/main/java/cc/backend/amateurShow/controller/AmateurController.java
  • src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java
  • src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java
  • src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurService.java
  • src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
  • src/main/java/cc/backend/photoAlbum/controller/PhotoAlbumController.java
  • src/main/java/cc/backend/photoAlbum/dto/PhotoAlbumResponseDTO.java
  • src/main/java/cc/backend/photoAlbum/service/PhotoAlbumService.java
  • src/main/java/cc/backend/photoAlbum/service/PhotoAlbumServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/cc/backend/photoAlbum/service/PhotoAlbumServiceImpl.java (1)
src/main/java/cc/backend/photoAlbum/dto/PhotoAlbumResponseDTO.java (1)
  • PhotoAlbumResponseDTO (11-87)
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java (3)
src/main/java/cc/backend/amateurShow/converter/AmateurConverter.java (1)
  • AmateurConverter (17-310)
src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java (1)
  • AmateurShowSpecification (12-88)
src/main/java/cc/backend/amateurShow/dto/AmateurShowResponseDTO.java (1)
  • AmateurShowResponseDTO (12-122)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (19)
src/main/java/cc/backend/photoAlbum/service/PhotoAlbumService.java (2)

22-22: LGTM!

The new getMyShows method signature is clear and follows the existing patterns in the service interface.


20-20: All callers have been properly updated for the breaking signature change.

The search for remaining calls using the old three-argument signature returned no results. The codebase consistently uses the new signature getAllRecentPhotoAlbumList(Long cursorId, int size) across the interface definition, implementation, and the identified call site in PhotoAlbumController.

src/main/java/cc/backend/photoAlbum/service/PhotoAlbumServiceImpl.java (2)

268-311: LGTM!

The refactoring to remove member context from getAllRecentPhotoAlbumList correctly enables visitor-first access. The cursor-based pagination logic is sound, and the N+1 query prevention with batch image retrieval is well-implemented.


378-378: The method findAllByMemberId(Long memberId) is properly declared in the AmateurShowRepository interface at line 61 of src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java. The code at line 378 correctly calls this existing method.

src/main/java/cc/backend/photoAlbum/controller/PhotoAlbumController.java (3)

32-38: LGTM!

The new endpoint is well-structured with proper security authorization, clear documentation, and correct delegation to the service layer.


78-78: LGTM!

Adding @PreAuthorize("hasRole('PERFORMER')") to the delete endpoint ensures proper authorization and aligns with security best practices for mutating operations.


92-92: LGTM!

The service call correctly updated to match the new signature without the memberId parameter, enabling visitor-first access to the photo album list.

src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java (2)

61-61: LGTM!

The new findAllByMemberId method provides a simple member-based query for retrieving all shows, supporting the photo album API use case.


63-73: Good addition of approval status filter.

Adding AND s.approvalStatus = 'APPROVED' ensures only approved shows appear in the hot shows list, which aligns with the PR objective of restricting general user access to approved content only.

src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java (2)

22-25: LGTM!

The isApproved() specification correctly filters shows by ApprovalStatus.APPROVED, providing a clean replacement for the previous isAudienceVisible() method.


50-61: No action required — application currently uses MySQL exclusively.

Verification shows the application uses only MySQL across all environments (dev, prod, and tests). The build.gradle includes only the MySQL connector (runtimeOnly 'com.mysql:mysql-connector-j') with no H2, PostgreSQL, TestContainers, or other database dependencies. Test configurations do not override the database setup. The MySQL-specific cb.function("DATE", ...) is not a portability concern in the current context since no multi-database support is required or planned.

src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurService.java (1)

18-25: LGTM!

The removal of memberId from these method signatures correctly reflects the shift to member-agnostic public endpoints. These audience-facing methods now rely on database-level filtering via Specifications rather than member-based authorization.

src/main/java/cc/backend/amateurShow/controller/AmateurController.java (1)

59-61: LGTM!

The endpoint correctly removes member context, making it a public API that relies on database-level filtering for approval status.

src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java (6)

6-6: LGTM!

The new imports for ApprovalStatus, AmateurShowSpecification, Specification, and Spring Data domain classes properly support the refactored Specification-based querying approach.

Also applies to: 11-11, 29-29, 31-31


446-451: LGTM!

The refactored getAmateurShow method correctly removes the unused memberId parameter. The method now simply fetches the show by ID and converts to DTO.


455-478: LGTM!

The getShowToday method is cleanly refactored to use Specifications for database-level filtering. Combining isApproved() and hasRoundOn(today) correctly filters for approved shows with rounds on the current date.


482-509: LGTM!

The getShowOngoing method correctly uses Specifications and creates a properly sorted Pageable for the query.


513-548: LGTM!

The ranking query correctly uses Specifications with multi-field sorting (sold tickets descending, start date ascending) and limits to 10 results.


617-639: LGTM!

The getRecentlyHotShow method correctly uses the repository's findHotShows method which already includes the approval status filter in the query.

Comment on lines 95 to 100
@GetMapping("/incoming")
@Operation(summary = "임박한 공연 조회 API")
public ApiResponse<SliceResponse<AmateurShowResponseDTO.AmateurShowList>> getShowIncoming(
@AuthenticationPrincipal(expression = "member") Member member, @ParameterObject Pageable pageable) {
Slice<AmateurShowList> sliceResult = amateurService.getShowToday(member.getId(), pageable);
public ApiResponse<SliceResponse<AmateurShowResponseDTO.AmateurShowList>> getShowIncoming(@ParameterObject Pageable pageable) {
Slice<AmateurShowList> sliceResult = amateurService.getShowToday(pageable);
return ApiResponse.onSuccess(SliceResponse.of(sliceResult));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

getShowIncoming delegates to getShowToday — likely incorrect.

The operation summary says "임박한 공연 조회 API" (upcoming/imminent shows), but the implementation calls getShowToday(pageable), which filters for shows with rounds happening today. These are semantically different:

  • "Incoming" typically means upcoming/soon-to-happen shows
  • "Today" means shows occurring today

This may be a copy-paste error or intentional reuse, but the API behavior doesn't match its description.

🤖 Prompt for AI Agents
In @src/main/java/cc/backend/amateurShow/controller/AmateurController.java
around lines 95-100, The getShowIncoming controller method currently calls
amateurService.getShowToday, which mismatches the operation summary for "임박한 공연
조회 API"; update AmateurController.getShowIncoming to call the correct service
method (e.g., amateurService.getShowIncoming or amateurService.getUpcomingShows)
that returns upcoming/imminent shows instead of today's shows — if that service
method does not exist, implement a new method in AmateurService (and its impl)
that queries for upcoming rounds and returns a Slice<AmateurShowList>, and
ensure the returned DTO/signature matches SliceResponse.of(...) used in the
controller.

Comment on lines 561 to 567
// Pageable로 start 기준 오름차순 정렬, limit 없이 전체 조회
Pageable sortedPage = PageRequest.of(
0,
Integer.MAX_VALUE,
Sort.by("start").ascending());

result.add(AmateurShowResponseDTO.AmateurShowList.builder()
.amateurShowId(show.getId())
.name(show.getName())
//.place(show.getPlace())
.detailAddress(show.getDetailAddress())
.schedule(schedule)
.posterImageUrl(show.getPosterImageUrl())
.build());
}
}
List<AmateurShow> shows = amateurShowRepository.findAll(spec, sortedPage).getContent();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Integer.MAX_VALUE as page size is risky.

Using Integer.MAX_VALUE as the page size could load an unbounded number of records into memory if many shows close on the same day. Consider using a reasonable upper bound or streaming/batching if truly unbounded results are needed.

🔎 Proposed fix
         // Pageable로 start 기준 오름차순 정렬, limit 없이 전체 조회
         Pageable sortedPage = PageRequest.of(
                 0,
-                Integer.MAX_VALUE,
+                1000,  // Set a reasonable upper bound
                 Sort.by("start").ascending());
📝 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.

Suggested change
// Pageable로 start 기준 오름차순 정렬, limit 없이 전체 조회
Pageable sortedPage = PageRequest.of(
0,
Integer.MAX_VALUE,
Sort.by("start").ascending());
result.add(AmateurShowResponseDTO.AmateurShowList.builder()
.amateurShowId(show.getId())
.name(show.getName())
//.place(show.getPlace())
.detailAddress(show.getDetailAddress())
.schedule(schedule)
.posterImageUrl(show.getPosterImageUrl())
.build());
}
}
List<AmateurShow> shows = amateurShowRepository.findAll(spec, sortedPage).getContent();
// Pageable로 start 기준 오름차순 정렬, limit 없이 전체 조회
Pageable sortedPage = PageRequest.of(
0,
1000, // Set a reasonable upper bound
Sort.by("start").ascending());
List<AmateurShow> shows = amateurShowRepository.findAll(spec, sortedPage).getContent();
🤖 Prompt for AI Agents
In
@src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
around lines 561-567, Using Integer.MAX_VALUE as the page size in PageRequest.of
risks loading an unbounded number of AmateurShow entities into memory; replace
this with a reasonable upper bound (e.g., a defined constant MAX_PAGE_SIZE or a
sensible cap like 1000) or switch to a streaming/batching approach using
repository methods that return a Stream or Pageable-backed iteration; update the
PageRequest.of call (currently constructing sortedPage) and/or change the call
path via amateurShowRepository.findAll(spec, ...) to use streaming (e.g., a
repository method returning Stream<AmateurShow>) or looped Pageable requests to
fetch results in chunks.

Comment on lines +374 to +376
if(member.getRole() != Role.PERFORMER){
throw new GeneralException(ErrorStatus.MEMBER_NOT_AUTHORIZED);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove redundant role check.

The role check here is redundant because the @PreAuthorize("hasRole('PERFORMER')") annotation on the controller endpoint (line 32 in PhotoAlbumController.java) already enforces this constraint. If a non-PERFORMER user reaches this service method, the security layer has failed.

🔎 Proposed fix to remove redundant check
     public List<PhotoAlbumResponseDTO.MyShowsForPhotoAlbumDTO> getMyShows(Long memberId) {
 
         Member member = memberRepository.findById(memberId)
                 .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND));
 
-        if(member.getRole() != Role.PERFORMER){
-            throw new GeneralException(ErrorStatus.MEMBER_NOT_AUTHORIZED);
-        }
-
         List<AmateurShow> amateurShows = amateurShowRepository.findAllByMemberId(memberId);
🤖 Prompt for AI Agents
In @src/main/java/cc/backend/photoAlbum/service/PhotoAlbumServiceImpl.java
around lines 374-376, The if-check in PhotoAlbumServiceImpl that throws
GeneralException when member.getRole() != Role.PERFORMER is redundant because
the controller already enforces @PreAuthorize("hasRole('PERFORMER')"); remove
the role check (the if block referencing member.getRole() and Role.PERFORMER)
from PhotoAlbumServiceImpl so the service relies on controller/security
annotations (PhotoAlbumController's @PreAuthorize) and leave any necessary
null/ownership checks only.

Comment on lines +392 to +409
private static String mergeScheduleWithoutDays(LocalDate start, LocalDate end) {
try {
if (start == null || end == null) {
return "";
}

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd");

String startStr = start.format(formatter);
String endStr = end.format(formatter);

// 최종 결과: "2025.10.02~2025.10.05"
return startStr + "~" + endStr;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor to eliminate code duplication and improve error handling.

This method duplicates the logic of the existing mergeSchedule method (imported at line 33). The main differences are:

  1. Date format: mergeSchedule includes day-of-week; this method doesn't
  2. Error handling: This method uses printStackTrace(), which is not production-ready

Consider these improvements:

  1. Code duplication: Extract the format pattern as a parameter or create a variant of mergeSchedule in the converter utility
  2. Error handling: Replace printStackTrace() with proper logging using a logger
🔎 Proposed refactor options

Option 1: Add format parameter to existing utility

In cc/backend/amateurShow/converter/AmateurConverter.java:

public static String mergeSchedule(LocalDate start, LocalDate end, String pattern) {
    try {
        if (start == null || end == null) {
            return "";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        String startStr = start.format(formatter);
        String endStr = end.format(formatter);
        return startStr + "~" + endStr;
    } catch (Exception e) {
        log.error("Failed to format schedule", e);
        return "";
    }
}

Option 2: Use proper logging instead of printStackTrace()

If keeping the method separate:

+    private static final Logger log = LoggerFactory.getLogger(PhotoAlbumServiceImpl.class);
+
     private static String mergeScheduleWithoutDays(LocalDate start, LocalDate end) {
         try {
             if (start == null || end == null) {
                 return "";
             }
 
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd");
 
             String startStr = start.format(formatter);
             String endStr   = end.format(formatter);
 
             // 최종 결과: "2025.10.02~2025.10.05"
             return startStr + "~" + endStr;
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Failed to format schedule without days for start={}, end={}", start, end, e);
             return "";
         }
     }

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java (1)

552-575: Consider adding pagination or a reasonable size limit.

The method calls findAll(spec, Sort) without pagination, which could return an unbounded number of shows if many close on the same day. While this may be acceptable for typical use cases, consider adding a size limit for safety.

🔎 Proposed fix with size limit
 public List<AmateurShowResponseDTO.AmateurShowList> getShowClosing() {
     LocalDate today = LocalDate.now();
     
     Specification<AmateurShow> spec = Specification
             .where(AmateurShowSpecification.isApproved())
             .and(AmateurShowSpecification.hasLastRoundOn(today));
     
-    List<AmateurShow> shows = amateurShowRepository.findAll(spec, Sort.by("start").ascending());
+    Pageable pageable = PageRequest.of(0, 100, Sort.by("start").ascending());
+    List<AmateurShow> shows = amateurShowRepository.findAll(spec, pageable).getContent();
+    
     // DTO 변환
     return shows.stream()
             .map(show -> {
                 String schedule = AmateurConverter.mergeSchedule(show.getStart(), show.getEnd());
                 return AmateurShowResponseDTO.AmateurShowList.builder()
                         .amateurShowId(show.getId())
                         .name(show.getName())
                         .detailAddress(show.getDetailAddress())
                         .schedule(schedule)
                         .posterImageUrl(show.getPosterImageUrl())
                         .build();
             })
             .collect(Collectors.toList());
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5f4bdf and be88370.

📒 Files selected for processing (2)
  • src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java
  • src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java (3)
src/main/java/cc/backend/amateurShow/converter/AmateurConverter.java (1)
  • AmateurConverter (17-310)
src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java (1)
  • AmateurShowSpecification (14-97)
src/main/java/cc/backend/amateurShow/dto/AmateurShowResponseDTO.java (1)
  • AmateurShowResponseDTO (12-122)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (6)
src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java (5)

446-451: LGTM! Public endpoint pattern confirmed.

The removal of memberId parameter aligns with making this a public endpoint. The implementation correctly fetches the show by ID with appropriate error handling.


455-478: Excellent refactoring to Specification-based querying.

The migration from in-memory filtering to database-level Specification queries improves performance and scalability. The use of Slice for pagination is appropriate for this use case.


482-509: LGTM! Consistent Specification pattern.

The explicit PageRequest construction with sorting ensures predictable results. The implementation follows the same clean pattern as other refactored methods.


513-548: Well-implemented ranking query with appropriate limits.

The use of PageRequest.of(0, 10, ...) with multi-field sorting correctly implements top-10 ranking logic. Converting the Slice to a List is appropriate since this method returns a fixed-size ranking.


610-632: LGTM! Appropriate direct query for simple use case.

The removal of memberId makes this a public endpoint, and the direct repository call with a fixed limit of 3 is appropriate for a "hot shows" feature. The implementation is clean and consistent with other DTO mapping.

src/main/java/cc/backend/amateurShow/repository/specification/AmateurShowSpecification.java (1)

48-96: This concern overstates the portability issue.

Both methods use cb.function("DATE", ...), which is appropriate for this MySQL-only application (as documented in the code comment "MySQL 기준"). PostgreSQL actually supports the DATE() function as a cast operator and would not fail. H2 has more limited support, but H2 is not part of this project's configuration (only MySQL is configured in build.gradle and application profiles).

Refactoring to use CAST or BETWEEN approaches would only be necessary if the application gains multi-database support in the future. For the current MySQL-only context, the existing implementation is idiomatic and correct.

Likely an incorrect or invalid review comment.

@sweatbuckets sweatbuckets merged commit 4098d73 into develop Jan 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants