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: 봉사 모집글 상세 조회 로직을 구현한다. #44

Merged
merged 15 commits into from
Nov 1, 2023
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
7 changes: 7 additions & 0 deletions coverage-exclude.anifriends
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
exclude com/clova/anifriends/domain/*/dto/response/*
exclude com/clova/anifriends/domain/*/dto/request/*
exclude com/clova/anifriends/domain/*/exception/*
exclude com/clova/anifriends/domain/*/Q*
exclude com/clova/anifriends/global/*
exclude com/clova/anifriends/domain/common/QBaseTimeEntity
exclude com/clova/anifriends/domain/*/wrapper/Q*
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public abstract class BaseTimeEntity {

@CreatedDate
@Column(name = "createdAt", updatable = false)
private LocalDateTime createdAt;
protected LocalDateTime createdAt;

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.clova.anifriends.domain.recruitment;

import com.clova.anifriends.domain.applicant.Applicant;
import com.clova.anifriends.domain.common.BaseTimeEntity;
import com.clova.anifriends.domain.recruitment.wrapper.RecruitmentContent;
import com.clova.anifriends.domain.recruitment.wrapper.RecruitmentInfo;
Expand All @@ -14,14 +15,22 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.LastModifiedDate;

@Entity
@Table(name = "recruitment")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Recruitment extends BaseTimeEntity {

public static final boolean IS_CLOSED_DEFAULT = false;

@Id
@Column(name = "recruitment_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -31,6 +40,12 @@ public class Recruitment extends BaseTimeEntity {
@JoinColumn(name = "shelter_id")
private Shelter shelter;

@OneToMany(mappedBy = "recruitment", fetch = FetchType.LAZY, orphanRemoval = true)
private List<RecruitmentImage> imageUrls = new ArrayList<>();

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

@Embedded
private RecruitmentTitle title;

Expand All @@ -44,28 +59,37 @@ public class Recruitment extends BaseTimeEntity {
@Column(name = "updated_at")
private LocalDateTime updatedAt;

protected Recruitment() {
}

public Recruitment(
Shelter shelter,
String title,
int capacity,
String content,
LocalDateTime startTime,
LocalDateTime endTime,
LocalDateTime deadline
LocalDateTime deadline,
List<String> imageUrls
) {
this.shelter = shelter;
this.title = new RecruitmentTitle(title);
this.content = new RecruitmentContent(content);
this.info = new RecruitmentInfo(startTime, endTime, deadline, false, capacity);
this.info = new RecruitmentInfo(startTime, endTime, deadline, IS_CLOSED_DEFAULT, capacity);
this.imageUrls = imageUrls.stream()
.map(url -> new RecruitmentImage(this, url))
.toList();
}

public Long getRecruitmentId() {
return recruitmentId;
}

public String getTitle() {
return title.getTitle();
}

public int getCapacity() {
return info.getCapacity();
}

public String getContent() {
return content.getContent();
}
Expand All @@ -78,15 +102,29 @@ public LocalDateTime getEndTime() {
return info.getEndTime();
}

public Boolean isClosed() {
return info.isClosed();
}

public LocalDateTime getDeadline() {
return info.getDeadline();
}

public int getCapacity() {
return info.getCapacity();
public LocalDateTime getCreatedAt() {
return createdAt;
}

public boolean isClosed() {
return info.isClosed();
public LocalDateTime getUpdatedAt() {
return updatedAt;
}

public List<String> getImageUrls() {
return imageUrls.stream()
.map(RecruitmentImage::getImageUrl)
.toList();
}

public int getApplicantCount() {
return applications.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Entity
@Table(name = "recruitment_image")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RecruitmentImage extends BaseTimeEntity {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.clova.anifriends.domain.recruitment.controller;

import com.clova.anifriends.domain.recruitment.dto.response.FindRecruitmentByShelterResponse;
import com.clova.anifriends.domain.recruitment.service.RecruitmentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

private final RecruitmentService recruitmentService;

@GetMapping("/shelters/recruitments/{recruitmentId}")
public ResponseEntity<FindRecruitmentByShelterResponse> findRecruitmentByIdByShelter(
@PathVariable Long recruitmentId) {
return ResponseEntity.ok(recruitmentService.findRecruitmentByIdByShelter(recruitmentId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.clova.anifriends.domain.recruitment.dto.response;

import com.clova.anifriends.domain.recruitment.Recruitment;
import java.time.LocalDateTime;
import java.util.List;

public record FindRecruitmentByShelterResponse(
String title,
int capacity,
int applicantCount,
String content,
LocalDateTime startTime,
LocalDateTime endTime,
boolean isClosed,
LocalDateTime deadline,
LocalDateTime createdAt,
LocalDateTime updatedAt,
List<String> imageUrls
) {

public static FindRecruitmentByShelterResponse from(Recruitment recruitment) {
return new FindRecruitmentByShelterResponse(
recruitment.getTitle(),
recruitment.getCapacity(),
recruitment.getApplicantCount(),
recruitment.getContent(),
recruitment.getStartTime(),
recruitment.getEndTime(),
recruitment.isClosed(),
recruitment.getDeadline(),
recruitment.getCreatedAt(),
recruitment.getUpdatedAt(),
recruitment.getImageUrls()
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.clova.anifriends.domain.recruitment.exception;

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

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

public class RecruitmentNotFoundException extends NotFoundException {

public RecruitmentNotFoundException(String message) {
super(NOT_FOUND, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.clova.anifriends.domain.recruitment.service;

import com.clova.anifriends.domain.recruitment.Recruitment;
import com.clova.anifriends.domain.recruitment.dto.response.FindRecruitmentByShelterResponse;
import com.clova.anifriends.domain.recruitment.exception.RecruitmentNotFoundException;
import com.clova.anifriends.domain.recruitment.repository.RecruitmentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RecruitmentService {

private final RecruitmentRepository recruitmentRepository;

public FindRecruitmentByShelterResponse findRecruitmentByIdByShelter(long id) {
Recruitment recruitment = getRecruitmentById(id);
return FindRecruitmentByShelterResponse.from(recruitment);
}

private Recruitment getRecruitmentById(long id) {
return recruitmentRepository.findById(id)
.orElseThrow(() -> new RecruitmentNotFoundException("존재하지 않는 모집글입니다."));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ public class RecruitmentInfo {
@Column(name = "capacity")
private int capacity;

public RecruitmentInfo(LocalDateTime startTime, LocalDateTime endTime, LocalDateTime deadline,
boolean isClosed, int capacity) {
public RecruitmentInfo(
LocalDateTime startTime,
LocalDateTime endTime,
LocalDateTime deadline,
boolean isClosed,
int capacity
) {
this.startTime = startTime;
this.endTime = endTime;
this.deadline = deadline;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.clova.anifriends.base.BaseControllerTest.WebMvcTestConfig;
import com.clova.anifriends.base.config.RestDocsConfig;
import com.clova.anifriends.domain.recruitment.service.RecruitmentService;
import com.clova.anifriends.domain.auth.authentication.JwtAuthenticationProvider;
import com.clova.anifriends.domain.auth.jwt.JwtProvider;
import com.clova.anifriends.domain.auth.service.AuthService;
Expand Down Expand Up @@ -72,6 +73,9 @@ public JwtAuthenticationProvider jwtAuthenticationProvider(JwtProvider jwtProvid
@Autowired
protected RestDocumentationResultHandler restDocs;

@MockBean
protected RecruitmentService recruitmentService;

@MockBean
protected AuthService authService;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -21,13 +22,14 @@ void newRecruitment() {
int capacity = 10;
String content = "content";
LocalDateTime now = LocalDateTime.now();
List<String> imageUrls = List.of("imageUrl1", "imageUrl2");
LocalDateTime startTime = now.plusHours(1);
LocalDateTime endTime = startTime.minusMinutes(1);
LocalDateTime deadline = now.plusMinutes(10);

//when
Recruitment recruitment = new Recruitment(
null, title, capacity, content, startTime, endTime, deadline);
null, title, capacity, content, startTime, endTime, deadline, imageUrls);

//then
assertThat(recruitment.getTitle()).isEqualTo(title);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.clova.anifriends.domain.recruitment.controller;

import static com.clova.anifriends.domain.recruitment.support.fixture.RecruitmentDtoFixture.findRecruitmentResponse;
import static com.clova.anifriends.domain.recruitment.support.fixture.RecruitmentFixture.recruitment;
import static com.clova.anifriends.domain.shelter.support.fixture.ShelterFixture.shelter;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.JsonFieldType.ARRAY;
import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
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.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.clova.anifriends.base.BaseControllerTest;
import com.clova.anifriends.domain.recruitment.Recruitment;
import com.clova.anifriends.domain.recruitment.dto.response.FindRecruitmentByShelterResponse;
import com.clova.anifriends.domain.shelter.Shelter;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.ResultActions;

class RecruitmentControllerTest extends BaseControllerTest {

@Test
@DisplayName("findRecruitmentById 실행 시")
void FindRecruitmentTest() throws Exception {
// given
Shelter shelter = shelter();
Recruitment recruitment = recruitment(shelter);
FindRecruitmentByShelterResponse response = findRecruitmentResponse(recruitment);

when(recruitmentService.findRecruitmentByIdByShelter(anyLong()))
.thenReturn(response);

// when
ResultActions result = mockMvc.perform(
get("/api/shelters/recruitments/{recruitmentId}", anyLong())
.contentType(MediaType.APPLICATION_JSON)
);

// then
result.andExpect(status().isOk())
.andDo(restDocs.document(
pathParameters(
parameterWithName("recruitmentId").description("봉사 모집글 ID")
),
responseFields(
fieldWithPath("title").type(STRING).description("제목"),
fieldWithPath("capacity").type(NUMBER).description("정원"),
fieldWithPath("applicantCount").type(NUMBER).description("봉사 신청 인원"),
fieldWithPath("content").type(STRING).description("내용"),
fieldWithPath("startTime").type(STRING).description("봉사 시작 시간"),
fieldWithPath("endTime").type(STRING).description("봉사 종료 시간"),
fieldWithPath("isClosed").type(BOOLEAN).description("마감 여부"),
fieldWithPath("deadline").type(STRING).description("마감 시간"),
fieldWithPath("deadline").type(STRING).description("마감 날짜와 시간"),
fieldWithPath("createdAt").type(STRING).description("게시글 생성 시간").optional(),
fieldWithPath("updatedAt").type(STRING).description("게시글 수정 시간").optional(),
fieldWithPath("imageUrls[]").type(ARRAY).description("이미지 url 리스트")
)
));

}

}
Loading