Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
04c36ac
add show detail retrieval feature with API documentation
YeaChan05 Sep 25, 2025
72b76be
add show detail inquiry endpoint and response model
YeaChan05 Sep 25, 2025
f2052e7
add show detail retrieval for non-existent showId with NOT_FOUND resp…
YeaChan05 Sep 25, 2025
209e0b0
add validation for showId to return BAD_REQUEST for non-positive values
YeaChan05 Sep 25, 2025
dc05f77
add parameterized tests for invalid showId and enhance error handling…
YeaChan05 Sep 25, 2025
7751cb6
add show detail retrieval for existing hall ID with successful response
YeaChan05 Sep 26, 2025
1238946
add retrieval of schedules for shows before the performance end date
YeaChan05 Sep 26, 2025
13ba93c
add runtime validation for show schedules to match start and end times
YeaChan05 Sep 26, 2025
94250fb
add test to ensure hall information includes both hallId and hallName
YeaChan05 Sep 26, 2025
774be53
remove hall information test for show detail retrieval
YeaChan05 Sep 26, 2025
ab4e893
add test to verify schedules are sorted by end time in show detail re…
YeaChan05 Sep 26, 2025
a66a866
add test to verify schedules are sorted by end time in show detail re…
YeaChan05 Sep 26, 2025
8e5fc2d
add test to verify retrieval of persisted show details
YeaChan05 Sep 26, 2025
9e95f8e
add test to verify retrieval of persisted show details
YeaChan05 Sep 26, 2025
95423c9
simplify ShowDetailResponse structure and update related methods
YeaChan05 Sep 27, 2025
58b6e87
complete show detail retrieval functionality and note performance con…
YeaChan05 Sep 27, 2025
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 @@ -18,6 +18,7 @@ public void authorizeRequests(
.requestMatchers("/api/auth/reissue").permitAll()
.requestMatchers(HttpMethod.POST, "/api/show/schedule").hasAuthority("ROLE_DISTRIBUTOR")
.requestMatchers(HttpMethod.GET, "/api/show").permitAll()
.requestMatchers(HttpMethod.GET, "/api/show/*").permitAll()// 인증이 필요한 GET /show/* 엔드포인트 추가시 설정을 이 줄 아래에
.requestMatchers(HttpMethod.POST, "/api/show").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package org.mandarin.booking.adapter.webapi;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import org.mandarin.booking.adapter.SliceView;
import org.mandarin.booking.app.show.ShowFetcher;
import org.mandarin.booking.app.show.ShowRegisterer;
import org.mandarin.booking.domain.show.ShowDetailResponse;
import org.mandarin.booking.domain.show.ShowInquiryRequest;
import org.mandarin.booking.domain.show.ShowRegisterRequest;
import org.mandarin.booking.domain.show.ShowRegisterResponse;
import org.mandarin.booking.domain.show.ShowResponse;
import org.mandarin.booking.domain.show.ShowScheduleRegisterRequest;
import org.mandarin.booking.domain.show.ShowScheduleRegisterResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -25,6 +28,13 @@ SliceView<ShowResponse> inquire(@Valid ShowInquiryRequest req) {
return showFetcher.fetchShows(req.page(), req.size(), req.type(), req.rating(), req.q(), req.from(), req.to());
}

@GetMapping("/{showId}")
ShowDetailResponse inquireDetail(@PathVariable
@Positive(message = "show Id는 음수일 수 없습니다.")
Long showId) {
return showFetcher.fetchShowDetail(showId);
}

@PostMapping
ShowRegisterResponse register(@RequestBody @Valid ShowRegisterRequest request) {
return showRegisterer.register(request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.mandarin.booking.app.hall;

import org.mandarin.booking.domain.hall.Hall;

public interface HallFetcher {
Hall fetch(Long hallId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.mandarin.booking.app.hall;

import lombok.RequiredArgsConstructor;
import org.mandarin.booking.domain.hall.Hall;
import org.mandarin.booking.domain.hall.HallException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -13,4 +15,9 @@ class HallQueryRepository {
boolean existsById(Long hallId) {
return repository.existsById(hallId);
}

public Hall findById(Long hallId) {
return repository.findById(hallId)
.orElseThrow(() -> new HallException("NOT_FOUND", "해당 공연장을 찾을 수 없습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.mandarin.booking.app.hall;

import java.util.Optional;
import org.mandarin.booking.domain.hall.Hall;
import org.springframework.data.repository.Repository;

interface HallRepository extends Repository<Hall, Long> {
Hall save(Hall hall);

boolean existsById(Long id);

Optional<Hall> findById(Long id);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package org.mandarin.booking.app.hall;

import lombok.RequiredArgsConstructor;
import org.mandarin.booking.domain.hall.Hall;
import org.mandarin.booking.domain.hall.HallException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
class HallService implements HallValidator {
class HallService implements HallValidator, HallFetcher {
private final HallQueryRepository queryRepository;

@Override
Expand All @@ -15,4 +16,9 @@ public void checkHallExist(Long hallId) {
throw new HallException("NOT_FOUND", "해당 공연장을 찾을 수 없습니다.");
}
}

@Override
public Hall fetch(Long hallId) {
return queryRepository.findById(hallId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import java.time.LocalDate;
import org.mandarin.booking.adapter.SliceView;
import org.mandarin.booking.domain.show.ShowDetailResponse;
import org.mandarin.booking.domain.show.ShowResponse;

public interface ShowFetcher {
SliceView<ShowResponse> fetchShows(Integer page, Integer size, String type, String rating, String q,
LocalDate from, LocalDate to);

ShowDetailResponse fetchShowDetail(Long showId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.adapter.SliceView;
import org.mandarin.booking.app.hall.HallFetcher;
import org.mandarin.booking.app.hall.HallValidator;
import org.mandarin.booking.domain.show.Show;
import org.mandarin.booking.domain.show.Show.Rating;
import org.mandarin.booking.domain.show.Show.ShowCreateCommand;
import org.mandarin.booking.domain.show.Show.Type;
import org.mandarin.booking.domain.show.ShowDetailResponse;
import org.mandarin.booking.domain.show.ShowException;
import org.mandarin.booking.domain.show.ShowRegisterRequest;
import org.mandarin.booking.domain.show.ShowRegisterResponse;
Expand All @@ -26,6 +28,7 @@ class ShowService implements ShowRegisterer, ShowFetcher {
private final ShowCommandRepository commandRepository;
private final ShowQueryRepository queryRepository;
private final HallValidator hallValidator;
private final HallFetcher hallFetcher;

@Override
public ShowRegisterResponse register(ShowRegisterRequest request) {
Expand Down Expand Up @@ -61,6 +64,25 @@ public SliceView<ShowResponse> fetchShows(Integer page, Integer size, String typ
q, from, to);
}

@Override
public ShowDetailResponse fetchShowDetail(Long showId) {
var show = queryRepository.findById(showId);
var hall = hallFetcher.fetch(show.getHallId());
return new ShowDetailResponse(
show.getId(),
show.getTitle(),
show.getType(),
show.getRating(),
show.getSynopsis(),
show.getPosterUrl(),
show.getPerformanceStartDate(),
show.getPerformanceEndDate(),
hall.getId(),
hall.getName(),
show.getScheduleResponses()
);
}

private void checkDuplicateTitle(String title) {
if (queryRepository.existsByName(title)) {
throw new ShowException("이미 존재하는 공연 이름입니다:" + title);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import jakarta.persistence.EntityManager;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;
import java.util.UUID;
Expand All @@ -21,7 +22,9 @@
import org.mandarin.booking.domain.show.Show.Rating;
import org.mandarin.booking.domain.show.Show.ShowCreateCommand;
import org.mandarin.booking.domain.show.Show.Type;
import org.mandarin.booking.domain.show.ShowDetailResponse.ShowScheduleResponse;
import org.mandarin.booking.domain.show.ShowRegisterRequest;
import org.mandarin.booking.domain.show.ShowScheduleCreateCommand;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -89,6 +92,23 @@ public Hall insertDummyHall() {
return hall;
}

public Show generateShow(int scheduleCount) {
var hall = insertDummyHall();
var show = generateShow(hall.getId());

for (int i = 0; i < scheduleCount; i++) {
Random random = new Random();
var startAt = LocalDateTime.now().plusDays(random.nextInt(0, 10));
var command = new ShowScheduleCreateCommand(show.getId(),
startAt,
startAt.plusHours(random.nextInt(2, 5))
);
show.registerSchedule(command);
}

return showInsert(show);
}

public List<Show> generateShows(int showCount) {
var hall = insertDummyHall();
return IntStream.range(0, showCount)
Expand Down Expand Up @@ -144,6 +164,33 @@ public void generateShows(int showCount, int before, int after) {
});
}

public Show generateShowWithNoSynopsis(int scheduleCount) {
var hall = insertDummyHall();
var show = Show.create(hall.getId(), ShowCreateCommand.from(new ShowRegisterRequest(
hall.getId(),
UUID.randomUUID().toString().substring(0, 10),
randomEnum(Type.class).name(),
randomEnum(Rating.class).name(),
null,
"https://example.com/poster.jpg",
LocalDate.now(),
LocalDate.now().plusDays(30)
)));

for (int i = 0; i < scheduleCount; i++) {
Random random = new Random();
var startAt = LocalDateTime.now().plusDays(random.nextInt(0, 10));
var command = new ShowScheduleCreateCommand(show.getId(),
startAt,
startAt.plusHours(random.nextInt(2, 5))
);
show.registerSchedule(command);
}

ReflectionTestUtils.setField(show, "synopsis", "");
return showInsert(show);
}

public boolean existsHallName(String name) {
return (entityManager.createQuery("SELECT COUNT(h) FROM Hall h WHERE h.name = :name")
.setParameter("name", name)
Expand All @@ -160,6 +207,20 @@ public Member findMemberByUserId(String userId) {
.getSingleResult();
}

public Hall findHallById(Long hallId) {
return entityManager.createQuery("SELECT h FROM Hall h WHERE h.id = :hallId", Hall.class)
.setParameter("hallId", hallId)
.getSingleResult();
}

public boolean isMatchingScheduleInShow(ShowScheduleResponse res, Show show) {
return !entityManager.createQuery(
"SELECT s FROM ShowSchedule s WHERE s.id = :scheduleId AND s.show.id = :showId", Object.class)
.setParameter("scheduleId", res.getScheduleId())
.setParameter("showId", show.getId())
.getResultList().isEmpty();
}

private void generateShow(Long hallId, Type type) {
var request = validShowRegisterRequest(hallId, type.name(), randomEnum(Rating.class).name());
var show = Show.create(hallId, ShowCreateCommand.from(request));
Expand All @@ -185,7 +246,7 @@ private void generateShow(Long hallId, Rating rating) {
showInsert(show);
}

private Show generateShow(Long hallId) {
public Show generateShow(Long hallId) {
var request = validShowRegisterRequest(hallId, randomEnum(Type.class).name(), randomEnum(Rating.class).name());
var show = Show.create(hallId, ShowCreateCommand.from(request));
return showInsert(show);
Expand Down
Loading