Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,6 @@ public ApiResponse<List<AmateurShowResponseDTO.AmateurShowList>> getShowClosing(
@GetMapping("/incoming")
@Operation(summary = "공연 임박인 공연 조회 API")
public ApiResponse<List<AmateurShowResponseDTO.AmateurShowList>> getShowIncoming(@AuthenticationPrincipal(expression = "member") Member member) {
return ApiResponse.onSuccess(amateurService.getIncomingShow(member.getId()));
return ApiResponse.onSuccess(amateurService.getRecentlyHotShow(member.getId()));
}
Comment on lines 92 to 96
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

Semantic mismatch between endpoint and implementation.

The endpoint /incoming with operation summary "공연 임박인 공연 조회 API" (retrieve upcoming shows) now calls getRecentlyHotShow(), which returns shows sorted by sales (hot shows), not by temporal proximity (incoming shows). This creates a confusing API contract.

Consider either:

  • Renaming the endpoint to /hot or /popular and updating the operation summary to reflect sales-based ranking
  • Or reverting the implementation to match the "incoming" semantic intent
🤖 Prompt for AI Agents
In src/main/java/cc/backend/amateurShow/controller/AmateurController.java around
lines 92-96, the /incoming endpoint and its Operation summary indicate "upcoming
shows" but the method calls amateurService.getRecentlyHotShow()
(sales/popularity-based), causing a semantic mismatch; fix by either (A)
renaming the endpoint and summary to reflect popularity (e.g.,
@GetMapping("/hot") and update Operation summary to "인기 공연 조회 API" or similar)
and keep calling getRecentlyHotShow(), or (B) change the service call to the
correct temporal-based method (e.g.,
amateurService.getIncomingShows(member.getId())) and ensure the Operation
summary remains "공연 임박인 공연 조회 API"; apply consistent naming to method, mapping,
and javadoc/annotations accordingly.

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,60 @@ public static List<AmateurCasting> toAmateurCastingEntity(List<AmateurEnrollRequ
AmateurShow amateurShow) {
if (castings == null || castings.isEmpty()) return Collections.emptyList();

return castings.stream().map(casting -> AmateurCasting.builder()
.amateurShow(amateurShow)
.actorName(casting.getActorName())
.castingName(casting.getCastingName())
.castingImageUrl(casting.getCastingImageRequestDTO().getImageUrl() != null ?
casting.getCastingImageRequestDTO().getImageUrl() : null)
.castingImageKeyName(casting.getCastingImageRequestDTO().getKeyName() != null ?
casting.getCastingImageRequestDTO().getKeyName() : null)
.build())
.collect(Collectors.toList());
return castings.stream()
.map(casting -> {
String imageUrl = null;
String keyName = null;

if (casting.getCastingImageRequestDTO() != null) {
imageUrl = casting.getCastingImageRequestDTO().getImageUrl();
keyName = casting.getCastingImageRequestDTO().getKeyName();
}

return AmateurCasting.builder()
.amateurShow(amateurShow)
.actorName(casting.getActorName())
.castingName(casting.getCastingName())
.castingImageUrl(imageUrl)
.castingImageKeyName(keyName)
.build();
})
.toList();
}

public static AmateurNotice toAmateurNoticeEntity(AmateurEnrollRequestDTO.Notice notice, AmateurShow amateurShow) {
if (notice.getContent() == null) return null;
if (notice == null || notice.getContent() == null) {
return null;
}

String imageUrl = null;
if (notice.getNoticeImageRequestDTO() != null) {
imageUrl = notice.getNoticeImageRequestDTO().getImageUrl();
}

return AmateurNotice.builder()
.amateurShow(amateurShow)
.content(notice.getContent())
.noticeImageUrl(notice.getNoticeImageRequestDTO().getImageUrl() != null ?
notice.getNoticeImageRequestDTO().getImageUrl() : null)
.noticeImageUrl(imageUrl)
.timeInfo(notice.getTimeInfo())
.build();
}

// 이건 수정용!!
public static AmateurNotice toAmateurNoticeEntity(AmateurUpdateRequestDTO.UpdateNotice notice, AmateurShow amateurShow) {
if (notice.getContent() == null) return null;
if (notice == null || notice.getContent() == null) {
return null;
}

String imageUrl = null;
if (notice.getNoticeImageRequestDTO() != null) {
imageUrl = notice.getNoticeImageRequestDTO().getImageUrl();
}

return AmateurNotice.builder()
.amateurShow(amateurShow)
.content(notice.getContent())
.noticeImageUrl(notice.getNoticeImageRequestDTO().getImageUrl() != null ?
notice.getNoticeImageRequestDTO().getImageUrl() : null)
.noticeImageUrl(imageUrl)
.timeInfo(notice.getTimeInfo())
.build();
}
Expand Down Expand Up @@ -135,12 +156,20 @@ public static List<AmateurRounds> toAmateurRoundEntity(List<AmateurEnrollRequest

// -- 소극장 공연 업데이트 --
public static AmateurCasting toSingleCasting(AmateurUpdateRequestDTO.UpdateCasting dto, AmateurShow show) {
String imageUrl = null;
String keyName = null;

if (dto.getCastingImageRequestDTO() != null) {
imageUrl = dto.getCastingImageRequestDTO().getImageUrl();
keyName = dto.getCastingImageRequestDTO().getKeyName();
}

return AmateurCasting.builder()
.amateurShow(show)
.actorName(dto.getActorName())
.castingName(dto.getCastingName())
.castingImageUrl(dto.getCastingImageRequestDTO().getImageUrl() != null ?
dto.getCastingImageRequestDTO().getImageUrl() : null)
.castingImageUrl(imageUrl)
.castingImageKeyName(keyName)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class AmateurCasting extends BaseEntity {
private String castingName;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "amateur_show_id")
@JoinColumn(name = "amateur_show_id", nullable = false)
private AmateurShow amateurShow;

public void update(AmateurUpdateRequestDTO.UpdateCasting dto) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ Slice<AmateurShow> findByMember_IdAndStatusInOrderByIdDesc(

List<AmateurShow> findByEndGreaterThanEqual(LocalDate today);

@Query("""
SELECT a
FROM AmateurShow a
WHERE a.end >= :today
ORDER BY a.totalSoldTicket DESC, a.start ASC
""")
List<AmateurShow> findHotShows(
@Param("today") LocalDate today,
Pageable pageable
);
Comment on lines +69 to +78
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if totalSoldTicket field allows null and get its declaration
rg -nP 'private\s+(Integer|Long)\s+totalSoldTicket' --type=java -A2 -B2

# Check for @Index annotations on AmateurShow entity
rg -n '@Index|@Table\(' src/main/java/cc/backend/amateurShow/entity/AmateurShow.java -A5

# Also check the full AmateurShow entity file for context
echo "=== Full AmateurShow entity file ==="
find . -path "*/amateurShow/entity/AmateurShow.java" -type f | head -1 | xargs cat

Repository: SeeATheater/CC_Backend

Length of output: 6042


Add COALESCE to ORDER BY clause and create a database index for the query.

The totalSoldTicket field is nullable (Integer type with no NOT NULL constraint), but the query orders by it without explicit null handling. SQL databases sort nulls first or last (not as 0), which differs from the entity's business logic that treats null as zero in increaseSoldTicket() and decreaseSoldTicket(). Update the query to ORDER BY COALESCE(a.totalSoldTicket, 0) DESC, a.start ASC to match the expected behavior.

Additionally, create a database index on totalSoldTicket (or a composite index on totalSoldTicket, start) to optimize the ordering and filtering performance.

🤖 Prompt for AI Agents
In src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java
around lines 69 to 78, the ORDER BY uses a.totalSoldTicket which is nullable and
should be treated as 0; change the JPQL to ORDER BY COALESCE(a.totalSoldTicket,
0) DESC, a.start ASC so nulls are treated as zero; additionally add a database
migration to create an index to optimize this query (either a single-column
index on total_sold_ticket or a composite index on (total_sold_ticket, start)
depending on your DB naming conventions) and ensure the migration is included in
your migrations folder and tested.


@Query("select a from AmateurShow a")
@EntityGraph(attributePaths = {"amateurRounds", "amateurNotice"}, type = EntityGraph.EntityGraphType.FETCH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ AmateurEnrollResponseDTO.AmateurEnrollResult enrollShow(Long memberId,
List<AmateurShowResponseDTO.AmateurShowList> getShowToday(Long memberId);
Slice<AmateurShowResponseDTO.AmateurShowList> getShowOngoing(Long memberId, Pageable pageable);
List<AmateurShowResponseDTO.AmateurShowList> getShowRanking(Long memberId);
List<AmateurShowResponseDTO.AmateurShowList> getIncomingShow(Long memberId);
List<AmateurShowResponseDTO.AmateurShowList> getRecentlyHotShow(Long memberId);
List<AmateurShowResponseDTO.AmateurShowList> getShowClosing(Long memberId);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import cc.backend.memberLike.repository.MemberLikeRepository;
import cc.backend.ticket.dto.response.ReserveListResponseDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Slice;
Expand Down Expand Up @@ -422,12 +423,19 @@ public void deleteShow(Long memberId, Long amateurShowId) {
}

//캐스팅 삭제
List<AmateurCasting> amateurCastings = amateurShow.getAmateurCastingList();
List<Image> castingImages = amateurCastings.stream()
.map(casting -> imageRepository.findByFilePathAndContentId(FilePath.casting, casting.getId()))
.filter(Objects::nonNull)
List<Long> castingIds = amateurShow.getAmateurCastingList()
.stream()
.map(AmateurCasting::getId)
.toList();
castingImages.forEach(image -> imageService.deleteImage(image.getId(), memberId));

if (!castingIds.isEmpty()) {
List<Image> castingImages =
imageRepository.findByFilePathAndContentIdIn(
FilePath.casting, castingIds);

castingImages.forEach(img ->
imageService.deleteImage(img.getId(), memberId));
}

//amateurShow 삭제
amateurShowRepository.delete(amateurShow);
Expand Down Expand Up @@ -621,32 +629,29 @@ public Slice<AmateurShowResponseDTO.MyShowAmateurShowList> getMyAmateurShow(Long
}

@Override
public List<AmateurShowResponseDTO.AmateurShowList> getIncomingShow (Long memberId){
public List<AmateurShowResponseDTO.AmateurShowList> getRecentlyHotShow (Long memberId){
memberRepository.findById(memberId).orElseThrow(()-> new GeneralException(ErrorStatus.MEMBER_NOT_AUTHORIZED));

Collator collator = Collator.getInstance(Locale.KOREAN); //한글 사전식 정렬

// 종료일이 오늘 이후인 공연만 DB에서 조회
// 종료되지 않은 공연 중, 판매량 기준 상위 3개
LocalDate today = LocalDate.now();
List<AmateurShow> shows = amateurShowRepository.findByEndGreaterThanEqual(today);
List<AmateurShow> shows = amateurShowRepository.findHotShows(today, PageRequest.of(0, 3));

return shows.stream()
// 시작일 기준 오름차순 → 이름 기준 오름차순(한글 사전식)
.sorted(Comparator
.comparing(AmateurShow::getStart)
.thenComparing(AmateurShow::getName, Comparator.nullsLast(collator)))
.limit(10)
.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());
.map(show -> AmateurShowResponseDTO.AmateurShowList.builder()
.amateurShowId(show.getId())
.name(show.getName())
.detailAddress(show.getDetailAddress())
.schedule(
AmateurConverter.mergeSchedule(
show.getStart(),
show.getEnd()
)
)
.posterImageUrl(show.getPosterImageUrl())
.build()
)
.toList();

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ List<Image> findFirstByContentIds(
<Optional> Image findByKeyName(String keyName);

<Optional> Image findByFilePathAndKeyName(FilePath filePath, String keyName);

List<Image> findByFilePathAndContentIdIn(
FilePath filePath,
List<Long> contentIds
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public NoticeResponseDTO.NoticeDTO notifyNewComment(CommentEvent event) {

memberNoticeRepository.save(MemberNotice.builder()
.notice(newNotice)
.member(commentWriter).build());
.member(boardWriter).build());

return NoticeResponseDTO.NoticeDTO.builder()
.id(newNotice.getId())
Expand Down