Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 봉사자 마이페이지 조회 로직을 구현한다. #46

Merged
merged 11 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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 @@ -2,6 +2,7 @@

import static com.clova.anifriends.global.exception.ErrorCode.BAD_REQUEST;

import com.clova.anifriends.domain.applicant.Applicant;
import com.clova.anifriends.domain.common.BaseTimeEntity;
import com.clova.anifriends.domain.volunteer.exception.VolunteerBadRequestException;
import com.clova.anifriends.domain.volunteer.wrapper.VolunteerEmail;
Expand All @@ -15,12 +16,18 @@
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

Expand Down Expand Up @@ -56,6 +63,12 @@ public class Volunteer extends BaseTimeEntity {
@Embedded
private VolunteerName name;

@OneToMany(mappedBy = "volunteer", fetch = FetchType.LAZY)
private List<Applicant> applications = new ArrayList<>();

@OneToOne(mappedBy = "volunteer")
private VolunteerImage volunteerImage;

public Volunteer(
String email,
String password,
Expand Down Expand Up @@ -112,4 +125,12 @@ public Integer getTemperature() {
public String getName() {
return this.name.getName();
}

public String getVolunteerImageUrl() {
return volunteerImage.getImageUrl();
}

public List<Applicant> getApplications() {
return Collections.unmodifiableList(applications);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.clova.anifriends.domain.volunteer;

import com.clova.anifriends.domain.common.BaseTimeEntity;
import com.clova.anifriends.domain.volunteer.exception.VolunteerBadRequestException;
import com.clova.anifriends.global.exception.ErrorCode;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -25,16 +27,29 @@ public class VolunteerImage extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long volunteerImageId;

@ManyToOne(fetch = FetchType.LAZY)
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "volunteer_id")
private Volunteer volunteer;

@Column(name = "image_url")
private String imageUrl;

public VolunteerImage(Volunteer volunteer, String imageUrl) {
validateVolunteer(volunteer);
this.volunteer = volunteer;
validateImageUrl(imageUrl);
this.imageUrl = imageUrl;
}
}

private void validateVolunteer(Volunteer value) {
if (value == null) {
throw new VolunteerBadRequestException(ErrorCode.BAD_REQUEST, "봉사자는 필수 항목입니다.");
}
}

private void validateImageUrl(String value) {
if (value == null || value.isBlank()) {
throw new VolunteerBadRequestException(ErrorCode.BAD_REQUEST, "이미지 url은 필수 항목입니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package com.clova.anifriends.domain.volunteer.controller;

import com.clova.anifriends.domain.volunteer.dto.request.RegisterVolunteerRequest;
import com.clova.anifriends.domain.volunteer.dto.response.FindVolunteerMyPageResponse;
import com.clova.anifriends.domain.volunteer.service.VolunteerService;
import jakarta.validation.Valid;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/volunteers")
@RequestMapping("/api")
public class VolunteerController {

private final VolunteerService volunteerService;
private static final String BASE_URI = "/api/volunteers/";

@PostMapping
@PostMapping("/volunteers")
public ResponseEntity<Void> registerVolunteer(
@RequestBody @Valid RegisterVolunteerRequest registerVolunteerRequest
) {
Expand All @@ -28,4 +31,10 @@ public ResponseEntity<Void> registerVolunteer(
return ResponseEntity.created(location).build();
}

@GetMapping("/volunteers/me")
public ResponseEntity<FindVolunteerMyPageResponse> findVolunteerMyPage(
@RequestHeader Long volunteerId // @UserId로 대체해야 함!
funnysunny08 marked this conversation as resolved.
Show resolved Hide resolved
) {
return ResponseEntity.ok(volunteerService.findVolunteerMyPage(volunteerId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.clova.anifriends.domain.volunteer.dto.response;

import com.clova.anifriends.domain.applicant.wrapper.ApplicantStatus;
import com.clova.anifriends.domain.volunteer.Volunteer;
import java.time.LocalDate;

public record FindVolunteerMyPageResponse(
String email,
String name,
LocalDate birthDate,
String phoneNumber,
int temperature,
long volunteerCount,
String imageUrl
) {

public static FindVolunteerMyPageResponse from(Volunteer volunteer) {
return new FindVolunteerMyPageResponse(
volunteer.getEmail(),
volunteer.getName(),
volunteer.getBirthDate(),
volunteer.getPhoneNumber(),
volunteer.getTemperature(),
volunteer.getApplications().stream()
.filter(applicant -> applicant.getStatus().equals(ApplicantStatus.ATTENDANCE))
.count(),
volunteer.getVolunteerImageUrl()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.clova.anifriends.domain.volunteer.exception;

import static com.clova.anifriends.global.exception.ErrorCode.NOT_FOUND;

import com.clova.anifriends.global.exception.NotFoundException;

public class VolunteerNotFoundException extends NotFoundException {

public VolunteerNotFoundException(String message) {
super(NOT_FOUND, message);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.clova.anifriends.domain.volunteer.repository;

import com.clova.anifriends.domain.volunteer.Volunteer;
import com.clova.anifriends.domain.volunteer.VolunteerImage;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface VolunteerImageRepository extends JpaRepository<VolunteerImage, Long> {

Optional<VolunteerImage> findByVolunteer(Volunteer volunteer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.clova.anifriends.domain.volunteer.Volunteer;
import com.clova.anifriends.domain.volunteer.dto.request.RegisterVolunteerRequest;
import com.clova.anifriends.domain.volunteer.dto.response.FindVolunteerMyPageResponse;
import com.clova.anifriends.domain.volunteer.exception.VolunteerNotFoundException;
import com.clova.anifriends.domain.volunteer.repository.VolunteerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -27,4 +29,14 @@ public Long registerVolunteer(RegisterVolunteerRequest registerVolunteerRequest)
volunteerRepository.save(volunteer);
return volunteer.getVolunteerId();
}

@Transactional(readOnly = true)
public FindVolunteerMyPageResponse findVolunteerMyPage(Long volunteerId) {
return FindVolunteerMyPageResponse.from(getVolunteer(volunteerId));
}

private Volunteer getVolunteer(Long volunteerId) {
return volunteerRepository.findById(volunteerId)
.orElseThrow(() -> new VolunteerNotFoundException("존재하지 않는 봉사자입니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.clova.anifriends.domain.volunteer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchException;

import com.clova.anifriends.domain.volunteer.exception.VolunteerBadRequestException;
import com.clova.anifriends.domain.volunteer.support.VolunteerFixture;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -10,19 +13,53 @@ class VolunteerImageTest {

@Nested
@DisplayName("VolunteerImage 생성 시")
class NewVolunteerImageTest {
class newVolunteerImageTest {

Volunteer volunteer;
String imageUrl;

@Test
@DisplayName("성공")
void newVolunteer() {
//given
String imageUrl = "www.aws.s3.com/2";
void success() {
// given
volunteer = VolunteerFixture.volunteer();
imageUrl = "url";

//when
VolunteerImage volunteerImage = new VolunteerImage(null, imageUrl);
// when
VolunteerImage volunteerImage = new VolunteerImage(volunteer, imageUrl);

//then
// then
assertThat(volunteerImage.getImageUrl()).isEqualTo(imageUrl);
}

@Test
@DisplayName("예외: imageUrl이 null")
void throwExceptionWhenImageUrlIsNull() {
// given
volunteer = VolunteerFixture.volunteer();

// when
Exception exception = catchException(
() -> new VolunteerImage(volunteer, imageUrl)
);

// then
assertThat(exception).isInstanceOf(VolunteerBadRequestException.class);
}

@Test
@DisplayName("예외: volunteer가 null인 경우")
void throwExceptionWhenVolunteerIsNull() {
// given
imageUrl = "url";

// when
Exception exception = catchException(
() -> new VolunteerImage(volunteer, imageUrl)
);

// then
assertThat(exception).isInstanceOf(VolunteerBadRequestException.class);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package com.clova.anifriends.domain.volunteer.controller;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.clova.anifriends.base.BaseControllerTest;
import com.clova.anifriends.domain.applicant.wrapper.ApplicantStatus;
import com.clova.anifriends.domain.volunteer.Volunteer;
import com.clova.anifriends.domain.volunteer.dto.request.RegisterVolunteerRequest;
import com.clova.anifriends.domain.volunteer.dto.response.FindVolunteerMyPageResponse;
import com.clova.anifriends.domain.volunteer.support.VolunteerDtoFixture;
import com.clova.anifriends.domain.volunteer.support.VolunteerFixture;
import com.clova.anifriends.domain.volunteer.support.VolunteerImageFixture;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
Expand Down Expand Up @@ -49,4 +58,43 @@ void registerVolunteer() throws Exception {
)
));
}

@Test
@DisplayName("봉사자 마이페이지 조회 API 호출 시")
void findVolunteerMyPage() throws Exception {
// given
Volunteer volunteer = VolunteerFixture.volunteer();
FindVolunteerMyPageResponse findVolunteerMyPageResponse = new FindVolunteerMyPageResponse(
volunteer.getEmail(),
volunteer.getName(),
volunteer.getBirthDate(),
volunteer.getPhoneNumber(),
volunteer.getTemperature(),
volunteer.getApplications().stream()
.filter(applicant -> applicant.getStatus().equals(ApplicantStatus.ATTENDANCE))
.count(),
VolunteerImageFixture.volunteerImage(volunteer).getImageUrl());
given(volunteerService.findVolunteerMyPage(anyLong())).willReturn(
findVolunteerMyPageResponse);

// when
ResultActions resultActions = mockMvc.perform(
get("/api/volunteers/me")
.header("volunteerId", 1L)
.contentType(MediaType.APPLICATION_JSON));

// then
resultActions.andExpect(status().isOk())
.andDo(restDocs.document(
responseFields(
fieldWithPath("email").type(STRING).description("이메일"),
fieldWithPath("name").type(STRING).description("이름"),
fieldWithPath("birthDate").type(STRING).description("생년월일"),
fieldWithPath("phoneNumber").type(STRING).description("전화번호"),
fieldWithPath("temperature").type(NUMBER).description("체온"),
fieldWithPath("volunteerCount").type(NUMBER).description("봉사 횟수"),
fieldWithPath("imageUrl").type(STRING).description("프로필 이미지 URL")
)
));
}
}
Loading