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
@@ -1,6 +1,7 @@
package org.festimate.team.api.matching;

import lombok.RequiredArgsConstructor;
import org.festimate.team.api.matching.dto.MatchingDetailInfo;
import org.festimate.team.api.matching.dto.MatchingListResponse;
import org.festimate.team.api.matching.dto.MatchingStatusResponse;
import org.festimate.team.domain.matching.service.MatchingService;
Expand Down Expand Up @@ -38,4 +39,16 @@ public ResponseEntity<ApiResponse<MatchingListResponse>> getMatching(
MatchingListResponse response = matchingService.getMatchingList(userId, festivalId);
return ResponseBuilder.ok(response);
}

@GetMapping("/{festivalId}/matchings/{matchingId}")
public ResponseEntity<ApiResponse<MatchingDetailInfo>> getMatchingDetail(
@RequestHeader("Authorization") String accessToken,
@PathVariable("festivalId") Long festivalId,
@PathVariable("matchingId") Long matchingId
) {
Long userId = jwtService.parseTokenAndGetUserId(accessToken);

MatchingDetailInfo response = matchingService.getMatchingDetail(userId, festivalId, matchingId);
return ResponseBuilder.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.festimate.team.api.matching.dto;

import org.festimate.team.domain.matching.entity.Matching;
import org.festimate.team.domain.participant.entity.Participant;
import org.festimate.team.domain.participant.entity.TypeResult;
import org.festimate.team.domain.user.entity.AppearanceType;
import org.festimate.team.domain.user.entity.Gender;
import org.festimate.team.domain.user.entity.Mbti;

public record MatchingDetailInfo(
String nickname,
Gender gender,
Integer birthYear,
Mbti mbti,
AppearanceType appearance,
String introduction,
String message,
TypeResult typeResult
) {
public static MatchingDetailInfo from(Matching matching) {
Participant participant = matching.getTargetParticipant();
return new MatchingDetailInfo(
participant.getUser().getNickname(),
participant.getUser().getGender(),
participant.getUser().getBirthYear(),
participant.getUser().getMbti(),
participant.getUser().getAppearanceType(),
participant.getIntroduction(),
participant.getMessage(),
participant.getTypeResult()
);
}
}
Comment on lines +10 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check to prevent potential NullPointerException.

The from method doesn't handle the case when matching.getTargetParticipant() is null, which could happen for PENDING matchings as seen in the test file where some matchings have null target participants.

Consider adding a null check before accessing the participant's properties:

public static MatchingDetailInfo from(Matching matching) {
    Participant participant = matching.getTargetParticipant();
+   if (participant == null) {
+       return new MatchingDetailInfo(
+           null, null, null, null, null, null, null, null
+       );
+   }
    return new MatchingDetailInfo(
            participant.getUser().getNickname(),
            participant.getUser().getGender(),
            participant.getUser().getBirthYear(),
            participant.getUser().getMbti(),
            participant.getUser().getAppearanceType(),
            participant.getIntroduction(),
            participant.getMessage(),
            participant.getTypeResult()
    );
}

Alternatively, you might want to create a separate DTO for pending matchings or throw an appropriate exception if this method should only be called for completed matchings.

Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@ boolean existsCompletedMatching(
""")
List<Matching> findAllMatchingsByApplicantParticipant(Participant participant);

Optional<Matching> findByMatchingId(Long matchingId);
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.festimate.team.domain.matching.service;

import org.festimate.team.api.matching.dto.MatchingDetailInfo;
import org.festimate.team.api.matching.dto.MatchingListResponse;
import org.festimate.team.api.matching.dto.MatchingStatusResponse;
import org.festimate.team.domain.participant.entity.Participant;
Expand All @@ -11,6 +12,8 @@ public interface MatchingService {

MatchingListResponse getMatchingList(Long userId, Long festivalId);

MatchingDetailInfo getMatchingDetail(Long userId, Long festivalId, Long matchingId);

Optional<Participant> findBestCandidateByPriority(long festivalId, Participant participant);

void matchPendingParticipants(Participant newParticipant);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.festimate.team.api.matching.dto.MatchingDetailInfo;
import org.festimate.team.api.matching.dto.MatchingInfo;
import org.festimate.team.api.matching.dto.MatchingListResponse;
import org.festimate.team.api.matching.dto.MatchingStatusResponse;
Expand All @@ -17,6 +18,8 @@
import org.festimate.team.domain.point.service.PointService;
import org.festimate.team.domain.user.entity.Gender;
import org.festimate.team.domain.user.service.UserService;
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;

Expand Down Expand Up @@ -66,6 +69,19 @@ public MatchingListResponse getMatchingList(Long userId, Long festivalId) {
return MatchingListResponse.from(matchings);
}

@Transactional(readOnly = true)
@Override
public MatchingDetailInfo getMatchingDetail(Long userId, Long festivalId, Long matchingId) {
Festival festival = festivalService.getFestivalByIdOrThrow(festivalId);
participantService.getParticipantOrThrow(userService.getUserByIdOrThrow(userId), festival);
Matching matching = matchingRepository.findByMatchingId(matchingId)
.orElseThrow(() -> new FestimateException(ResponseError.TARGET_NOT_FOUND));
if (!matching.getFestival().getFestivalId().equals(festivalId)) {
throw new FestimateException(ResponseError.FORBIDDEN_RESOURCE);
}
return MatchingDetailInfo.from(matching);
}

@Transactional
protected Matching saveMatching(Festival festival, Optional<Participant> targetParticipantOptional, Participant participant) {
Matching matching;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.festimate.team.domain.user.entity.Gender;
import org.festimate.team.domain.user.entity.User;
import org.festimate.team.domain.user.service.UserService;
import org.festimate.team.global.exception.FestimateException;
import org.festimate.team.global.response.ResponseError;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -26,11 +28,12 @@
import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.festimate.team.common.mock.MockFactory.*;
import static org.mockito.Mockito.when;

public class MatchingServiceImplTest {
class MatchingServiceImplTest {
@Mock
private MatchingRepository matchingRepository;

Expand Down Expand Up @@ -240,4 +243,31 @@ void findBestCandidateByPriority_empty() {
assertThat(result).isEmpty();
}

@Test
@DisplayName("매칭 상세 조회 - 매칭과 페스티벌이 일치하지 않는 경우 예외 발생")
void getMatchingDetail_invalidFestival_throwsException() {
// given
User user = mockUser("사용자", Gender.MAN, 1L);
Festival requestedFestival = mockFestival(user, 1L, LocalDate.now().minusDays(1), LocalDate.now().plusDays(1));
Festival otherFestival = mockFestival(user, 2L, LocalDate.now().minusDays(1), LocalDate.now().plusDays(1));

Participant participant = mockParticipant(user, requestedFestival, TypeResult.INFLUENCER, 1L);
Matching mismatchedMatching = Matching.builder()
.festival(otherFestival)
.applicantParticipant(participant)
.targetParticipant(null)
.status(MatchingStatus.PENDING)
.matchDate(LocalDateTime.now())
.build();

when(userService.getUserByIdOrThrow(user.getUserId())).thenReturn(user);
when(festivalService.getFestivalByIdOrThrow(requestedFestival.getFestivalId())).thenReturn(requestedFestival);
when(participantService.getParticipantOrThrow(user, requestedFestival)).thenReturn(participant);
when(matchingRepository.findByMatchingId(1L)).thenReturn(Optional.of(mismatchedMatching));

// when & then
assertThatThrownBy(() -> matchingService.getMatchingDetail(user.getUserId(), requestedFestival.getFestivalId(), 1L))
.isInstanceOf(FestimateException.class)
.hasMessage(ResponseError.FORBIDDEN_RESOURCE.getMessage());
}
}