Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public List<AdminFestivalResponse> getAllFestivals(Long userId) {

@Transactional(readOnly = true)
public AdminFestivalDetailResponse getFestivalDetail(Long userId, Long festivalId) {
Festival festival = festivalService.getFestivalDetailByIdOrThrow(festivalId, userId);
User user = userService.getUserByIdOrThrow(userId);
Festival festival = festivalService.getFestivalDetailByIdOrThrow(festivalId, user);
return AdminFestivalDetailResponse.of(festival);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.festimate.team.global.entity.BaseTimeEntity;
import org.festimate.team.domain.matching.entity.Matching;
import org.festimate.team.domain.participant.entity.Participant;
import org.festimate.team.domain.user.entity.User;
import org.festimate.team.global.entity.BaseTimeEntity;
import org.hibernate.annotations.DynamicUpdate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
Expand All @@ -26,9 +27,8 @@ public class Festival extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long festivalId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "host_id", nullable = false)
private User host;
@OneToMany(mappedBy = "festival", cascade = CascadeType.ALL, orphanRemoval = true)
private List<FestivalHost> festivalHosts = new ArrayList<>();

@Column(nullable = false)
private String title;
Expand Down Expand Up @@ -56,8 +56,7 @@ public class Festival extends BaseTimeEntity {
private List<Matching> matchings;

@Builder
public Festival(User host, String title, Category category, LocalDate startDate, LocalDate endDate, LocalDateTime matchingStartAt, String inviteCode) {
this.host = host;
public Festival(String title, Category category, LocalDate startDate, LocalDate endDate, LocalDateTime matchingStartAt, String inviteCode) {
this.title = title;
this.category = category;
this.startDate = startDate;
Expand Down Expand Up @@ -90,4 +89,22 @@ public FestivalStatus getMatchingStartTimeStatus() {
return FestivalStatus.BEFORE;
} else return getFestivalStatus();
}

public void addHost(User user) {
boolean alreadyExists = festivalHosts.stream()
.anyMatch(fh -> fh.getHost().getUserId().equals(user.getUserId()));

if (!alreadyExists) {
FestivalHost host = FestivalHost.builder()
.festival(this)
.host(user)
.build();
festivalHosts.add(host);
}
}

public void removeHost(User user) {
festivalHosts.removeIf(fh -> fh.getHost().equals(user));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.festimate.team.domain.festival.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.festimate.team.domain.user.entity.User;

import java.time.LocalDateTime;
import java.util.Objects;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(
name = "festival_host",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_festival_host_festival_user",
columnNames = {"festival_id", "user_id"}
)
}
)
public class FestivalHost {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long festivalHostId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "festival_id", nullable = false)
private Festival festival;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User host;

@Column(nullable = false)
private LocalDateTime addedAt;

@Builder
public FestivalHost(Festival festival, User host) {
this.festival = festival;
this.host = host;
this.addedAt = LocalDateTime.now();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FestivalHost)) return false;
FestivalHost that = (FestivalHost) o;
return Objects.equals(
this.festival != null ? this.festival.getFestivalId() : null,
that.festival != null ? that.festival.getFestivalId() : null) &&
Objects.equals(
this.host != null ? this.host.getUserId() : null,
that.host != null ? that.host.getUserId() : null);
}

@Override
public int hashCode() {
return Objects.hash(
festival != null ? festival.getFestivalId() : null,
host != null ? host.getUserId() : null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ public interface FestivalRepository extends JpaRepository<Festival, Integer> {

Optional<Festival> findByFestivalId(Long festivalId);

List<Festival> findFestivalByHost(User host);
List<Festival> findDistinctByFestivalHosts_Host(User host);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface FestivalService {

List<Festival> getAllFestivals(User user);

Festival getFestivalDetailByIdOrThrow(Long festivalId, Long userId);
Festival getFestivalDetailByIdOrThrow(Long festivalId, User user);

boolean isHost(User user, Festival festival);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.festimate.team.global.response.ResponseError;
import org.festimate.team.global.exception.FestimateException;
import org.festimate.team.api.admin.dto.FestivalRequest;
import org.festimate.team.domain.festival.entity.Category;
import org.festimate.team.domain.festival.entity.Festival;
import org.festimate.team.domain.festival.repository.FestivalRepository;
import org.festimate.team.domain.festival.service.FestivalService;
import org.festimate.team.domain.user.entity.User;
import org.festimate.team.global.exception.FestimateException;
import org.festimate.team.global.response.ResponseError;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
import java.util.Random;

import static org.festimate.team.domain.festival.validator.DateValidator.isFestivalDateValid;
Expand All @@ -35,7 +34,6 @@ public Festival createFestival(User host, FestivalRequest request) {
String inviteCode = generateUniqueInviteCode().trim();

Festival festival = Festival.builder()
.host(host)
.title(request.title())
.category(Category.toCategory(request.category()))
.startDate(request.startDate())
Expand All @@ -44,6 +42,8 @@ public Festival createFestival(User host, FestivalRequest request) {
.inviteCode(inviteCode)
.build();

festival.addHost(host);

return festivalRepository.save(festival);
}

Expand All @@ -66,22 +66,22 @@ public Festival getFestivalByIdOrThrow(Long festivalId) {
@Override
public List<Festival> getAllFestivals(User user) {
log.info("user: {}", user);
return festivalRepository.findFestivalByHost(user);
return festivalRepository.findDistinctByFestivalHosts_Host(user);
}

@Override
public Festival getFestivalDetailByIdOrThrow(Long festivalId, Long userId) {
public Festival getFestivalDetailByIdOrThrow(Long festivalId, User user) {
Festival festival = getFestivalByIdOrThrow(festivalId);
if (!Objects.equals(festival.getHost().getUserId(), userId)) {
if (!isHost(user, festival)) {
throw new FestimateException(ResponseError.FORBIDDEN_RESOURCE);
}
return festival;
}

@Override
public boolean isHost(User user, Festival festival) {
log.info("user is: {}, host is {}", user, festival.getHost());
return festival.getHost().equals(user);
return festival.getFestivalHosts().stream()
.anyMatch(fh -> fh.getHost().getUserId().equals(user.getUserId()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.festimate.team.global.util.DateFormatter.formatPeriod;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

class FestivalFacadeTest {
Expand Down Expand Up @@ -147,7 +149,8 @@ void getFestivalDetail_success() {
// given
Festival festival = MockFactory.mockFestival(user, 1L, LocalDate.now(), LocalDate.now().plusDays(1));

when(festivalService.getFestivalDetailByIdOrThrow(1L, 1L)).thenReturn(festival);
when(userService.getUserByIdOrThrow(eq(1L))).thenReturn(user);
when(festivalService.getFestivalDetailByIdOrThrow(eq(1L), any(User.class))).thenReturn(festival); // ⭐ 핵심

// when
var response = festivalFacade.getFestivalDetail(1L, 1L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.festimate.team.domain.festival.entity.Category;
import org.festimate.team.domain.festival.entity.Festival;
import org.festimate.team.domain.festival.entity.FestivalHost;
import org.festimate.team.domain.participant.entity.Participant;
import org.festimate.team.domain.participant.entity.TypeResult;
import org.festimate.team.domain.user.entity.*;
Expand Down Expand Up @@ -30,7 +31,6 @@ public static User mockUser(String nickname, Gender gender, long id) {

public static Festival mockFestival(User host, long id, LocalDate startDate, LocalDate endDate) {
Festival festival = Festival.builder()
.host(host)
.title("모의 페스티벌")
.category(Category.LIFE)
.startDate(startDate)
Expand All @@ -39,6 +39,11 @@ public static Festival mockFestival(User host, long id, LocalDate startDate, Loc
.inviteCode("MOCK123")
.build();

FestivalHost festivalHost = FestivalHost.builder()
.festival(festival)
.host(host)
.build();

ReflectionTestUtils.setField(festival, "festivalId", id);
return festival;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void getAllFestivals_success() {
Festival festival2 = MockFactory.mockFestival(host, 2L, LocalDate.now().minusDays(1), LocalDate.now().plusDays(1));
List<Festival> expectedFestivals = List.of(festival1, festival2);

when(festivalRepository.findFestivalByHost(host)).thenReturn(expectedFestivals);
when(festivalRepository.findDistinctByFestivalHosts_Host(host)).thenReturn(expectedFestivals);

// when
List<Festival> result = festivalService.getAllFestivals(host);
Expand All @@ -48,4 +48,31 @@ void getAllFestivals_success() {
assertThat(result).hasSize(2);
assertThat(result).containsExactly(festival1, festival2);
}


@Test
@DisplayName("동일한 페스티벌에 여러 호스트가 있을 때, 각 호스트의 조회 결과에 동일한 페스티벌이 반환된다")
void getAllFestivals_multipleHosts_returnSameFestival() {
// given
User host1 = MockFactory.mockUser("1호스트", Gender.MAN, 1L);
User host2 = MockFactory.mockUser("2호스트", Gender.MAN, 2L);
Festival festival = MockFactory.mockFestival(host1, 1L, LocalDate.now(), LocalDate.now().plusDays(2));
festival.addHost(host2);

List<Festival> expectedFestival1 = List.of(festival);
List<Festival> expectedFestival2 = List.of(festival);

when(festivalRepository.findDistinctByFestivalHosts_Host(host1)).thenReturn(expectedFestival1);
when(festivalRepository.findDistinctByFestivalHosts_Host(host2)).thenReturn(expectedFestival2);

// when
List<Festival> result1 = festivalService.getAllFestivals(host1);
List<Festival> result2 = festivalService.getAllFestivals(host2);

// then
assertThat(result1).hasSize(1);
assertThat(result2).hasSize(1);
assertThat(result1).containsExactly(festival);
assertThat(result2).containsExactly(festival);
}
}