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

[스테디] 스테디 인기 모집글 조회 API 개발 #197

Merged
merged 9 commits into from
Jan 3, 2024
12 changes: 12 additions & 0 deletions src/main/java/dev/steady/steady/controller/SteadyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import dev.steady.global.auth.UserInfo;
import dev.steady.steady.domain.SteadyStatus;
import dev.steady.steady.dto.FilterConditionDto;
import dev.steady.steady.dto.request.RankParams;
import dev.steady.steady.dto.request.SteadyCreateRequest;
import dev.steady.steady.dto.request.SteadyPageRequest;
import dev.steady.steady.dto.request.SteadyQuestionUpdateRequest;
Expand All @@ -13,9 +14,11 @@
import dev.steady.steady.dto.response.MySteadyResponse;
import dev.steady.steady.dto.response.PageResponse;
import dev.steady.steady.dto.response.ParticipantsResponse;
import dev.steady.steady.dto.RankCondition;
import dev.steady.steady.dto.response.SteadyDetailResponse;
import dev.steady.steady.dto.response.SteadyQueryResponse;
import dev.steady.steady.dto.response.SteadyQuestionsResponse;
import dev.steady.steady.dto.response.SteadyRankResponse;
import dev.steady.steady.service.SteadyService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -32,6 +35,7 @@
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;
import java.util.List;

@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -67,6 +71,14 @@ public ResponseEntity<PageResponse<SteadyQueryResponse>> getSteadies(@Auth(requi
return ResponseEntity.ok(response);
}

@GetMapping("/rank")
public ResponseEntity<List<SteadyRankResponse>> findPopularStudies(@Valid RankParams params) {
RankCondition condition = params.toCondition();
List<SteadyRankResponse> popularStudy = steadyService.findPopularStudies(condition);

return ResponseEntity.ok(popularStudy);
}

@GetMapping("/{steadyId}")
public ResponseEntity<SteadyDetailResponse> getDetailSteady(@PathVariable Long steadyId,
@Auth(required = false) UserInfo userInfo) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/dev/steady/steady/domain/Participants.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import dev.steady.user.domain.User;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Embeddable;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand All @@ -20,7 +21,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Participants {

@OneToMany(mappedBy = "steady", cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(mappedBy = "steady", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private final List<Participant> steadyParticipants = new ArrayList<>();

private int participantLimit;
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/dev/steady/steady/dto/RankCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.steady.steady.dto;

import java.time.LocalDate;

public record RankCondition(
LocalDate date,
int limit,
RankType type
) {

public static RankCondition of(LocalDate date, int limit, String type) {
return new RankCondition(date, limit, RankType.from(type));
}

}
13 changes: 13 additions & 0 deletions src/main/java/dev/steady/steady/dto/RankType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dev.steady.steady.dto;

public enum RankType {

ALL,
STUDY,
PROJECT;

public static RankType from(String type) {
return valueOf(type.toUpperCase());
}

}
21 changes: 21 additions & 0 deletions src/main/java/dev/steady/steady/dto/request/RankParams.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.steady.steady.dto.request;

import dev.steady.steady.dto.RankCondition;
import jakarta.validation.constraints.Max;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDate;

public record RankParams(
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate date,
@Max(value = 10, message = "인기글은 최대 10개까지 조회가 가능합니다.")
int limit,
String type
) {
Comment on lines +9 to +15
Copy link
Contributor

Choose a reason for hiding this comment

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

limit에 Max 값을 지정해줌으로써 예상치 못한 서버 에러를 방지하는 모습이 좋은 것 같습니다!


public RankCondition toCondition() {
return RankCondition.of(date, limit, type);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dev.steady.steady.dto.response;

import dev.steady.steady.domain.Steady;
import dev.steady.steady.domain.SteadyStatus;
import dev.steady.steady.domain.SteadyType;

import java.time.LocalDate;

public record SteadyRankResponse(
Long steadyId,
String title,
SteadyType type,
SteadyStatus status,
LocalDate deadline,
int viewCount,
int likeCount
) {

public static SteadyRankResponse from(Steady steady) {
return new SteadyRankResponse(
steady.getId(),
steady.getTitle(),
steady.getType(),
steady.getStatus(),
steady.getDeadline(),
steady.getViewCount(),
steady.getLikeCount()
);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.steady.steady.infrastructure;

import dev.steady.global.auth.UserInfo;
import dev.steady.steady.dto.RankCondition;
import dev.steady.steady.domain.Steady;
import dev.steady.steady.domain.SteadyStatus;
import dev.steady.steady.dto.FilterConditionDto;
Expand All @@ -10,10 +11,14 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

import java.util.List;

public interface SteadySearchRepository {

Page<Steady> findAllByFilterCondition(UserInfo userInfo, FilterConditionDto condition, Pageable pageable);

Slice<MySteadyQueryResponse> findMySteadies(SteadyStatus status, User user, Pageable pageable);

List<Steady> findPopularStudyInCondition(RankCondition condition);

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import dev.steady.steady.domain.Participant;
import dev.steady.steady.domain.Steady;
import dev.steady.steady.domain.SteadyStatus;
import dev.steady.steady.domain.SteadyType;
import dev.steady.steady.dto.FilterConditionDto;
import dev.steady.steady.dto.RankCondition;
import dev.steady.steady.dto.RankType;
import dev.steady.steady.dto.response.MySteadyQueryResponse;
import dev.steady.steady.uitl.Cursor;
import dev.steady.user.domain.User;
Expand All @@ -33,6 +36,8 @@
import static dev.steady.steady.domain.SteadyStatus.CLOSED;
import static dev.steady.steady.domain.SteadyStatus.FINISHED;
import static dev.steady.steady.domain.SteadyStatus.RECRUITING;
import static dev.steady.steady.dto.RankType.PROJECT;
import static dev.steady.steady.dto.RankType.STUDY;
import static dev.steady.steady.infrastructure.util.DynamicQueryUtils.filterCondition;
import static dev.steady.steady.infrastructure.util.DynamicQueryUtils.orderBySort;

Expand Down Expand Up @@ -90,6 +95,18 @@ public Slice<MySteadyQueryResponse> findMySteadies(SteadyStatus status, User use
return new SliceImpl<>(content, pageable, hasNext);
}

@Override
public List<Steady> findPopularStudyInCondition(RankCondition condition) {
return jpaQueryFactory.selectFrom(steady)
.where(steady.promotion.promotedAt
.after(condition.date().atStartOfDay()),
Copy link
Contributor

Choose a reason for hiding this comment

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

atStartOfDay로 자료형을 맞춰주시는 부분이 좋은 것 같네요!

steadyTypeCond(condition.type())
)
.orderBy(steady.likeCount.desc(), steady.viewCount.desc())
.limit(condition.limit())
.fetch();
}

private BooleanExpression isParticipantUserIdEqual(User user) {
return participant.user.id.eq(user.getId());
}
Expand All @@ -113,6 +130,16 @@ private BooleanExpression isWorkSteady(SteadyStatus status) {
return null;
}

private BooleanExpression steadyTypeCond(RankType type) {
if (type == PROJECT) {
return steady.type.eq(SteadyType.PROJECT);
}
if (type == STUDY) {
return steady.type.eq(SteadyType.STUDY);
}
return null;
}

private BooleanBuilder filterConditionBuilder(UserInfo userInfo, FilterConditionDto condition) {
BooleanBuilder booleanBuilder = new BooleanBuilder();

Expand Down
13 changes: 12 additions & 1 deletion src/main/java/dev/steady/steady/service/SteadyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import dev.steady.application.dto.response.SliceResponse;
import dev.steady.global.auth.UserInfo;
import dev.steady.global.exception.InvalidStateException;
import dev.steady.steady.dto.RankCondition;
import dev.steady.steady.domain.Participant;
import dev.steady.steady.domain.Steady;
import dev.steady.steady.domain.SteadyPosition;
Expand All @@ -28,6 +29,7 @@
import dev.steady.steady.dto.response.SteadyDetailResponse;
import dev.steady.steady.dto.response.SteadyQueryResponse;
import dev.steady.steady.dto.response.SteadyQuestionsResponse;
import dev.steady.steady.dto.response.SteadyRankResponse;
import dev.steady.user.domain.Position;
import dev.steady.user.domain.Stack;
import dev.steady.user.domain.User;
Expand Down Expand Up @@ -87,6 +89,15 @@ public PageResponse<SteadyQueryResponse> getSteadies(UserInfo userInfo, FilterCo
return PageResponse.from(searchResponses);
}

@Transactional(readOnly = true)
public List<SteadyRankResponse> findPopularStudies(RankCondition condition) {
List<Steady> steadies = steadyRepository.findPopularStudyInCondition(condition);

return steadies.stream()
.map(SteadyRankResponse::from)
.toList();
}

@Transactional
public SteadyDetailResponse getDetailSteady(Long steadyId, UserInfo userInfo) {
Steady steady = steadyRepository.getSteady(steadyId);
Expand Down Expand Up @@ -133,7 +144,7 @@ public void updateSteady(Long steadyId, SteadyUpdateRequest request, UserInfo us
steady.update(user,
request.name(),
request.bio(),
request.contact(),
request.contact(),
request.type(),
request.status(),
request.participantLimit(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
import dev.steady.global.auth.UserInfo;
import dev.steady.global.config.ControllerTestConfig;
import dev.steady.steady.dto.FilterConditionDto;
import dev.steady.steady.dto.RankCondition;
import dev.steady.steady.dto.request.RankParams;
import dev.steady.steady.dto.request.SteadyPageRequest;
import dev.steady.steady.dto.request.SteadyQuestionUpdateRequest;
import dev.steady.steady.dto.response.MySteadyResponse;
import dev.steady.steady.dto.response.ParticipantsResponse;
import dev.steady.steady.dto.response.SteadyDetailResponse;
import dev.steady.steady.dto.response.SteadyQuestionsResponse;
import dev.steady.steady.dto.response.SteadyRankResponse;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Pageable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

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

import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
Expand All @@ -31,6 +35,7 @@
import static dev.steady.steady.fixture.SteadyFixturesV2.createSteadyPageResponse;
import static dev.steady.steady.fixture.SteadyFixturesV2.createSteadyPosition;
import static dev.steady.steady.fixture.SteadyFixturesV2.createSteadyQuestionsResponse;
import static dev.steady.steady.fixture.SteadyFixturesV2.createSteadyRankResponses;
import static dev.steady.steady.fixture.SteadyFixturesV2.generateSteadyCreateRequest;
import static dev.steady.steady.fixture.SteadyFixturesV2.generateSteadyUpdateRequest;
import static dev.steady.user.fixture.UserFixturesV2.generatePositionEntity;
Expand Down Expand Up @@ -148,6 +153,42 @@ void findMySteadiesTest() throws Exception {
.andExpect(content().string(objectMapper.writeValueAsString(response)));
}

@Test
@DisplayName("조건에 맞는 인기 게시물 리스트를 반환합니다.")
void findPopularStudyTest() throws Exception {
// given
var params = new RankParams(LocalDate.of(2023, 12, 31), 10, "ALL");

RankCondition condition = params.toCondition();
List<SteadyRankResponse> response = createSteadyRankResponses();
given(steadyService.findPopularStudies(condition)).willReturn(response);

// when & then
mockMvc.perform(get("/api/v1/steadies/rank")
.param("date", "2023-12-31")
.param("limit", "10")
.param("type", "ALL")
)
.andDo(document("popular-steady",
queryParameters(
parameterWithName("date").description("기준 날짜 이후 생성 스테디"),
parameterWithName("limit").description("제한 10개 이하 가능"),
parameterWithName("type").description("ALL, STUDY, PROJECT")
),
responseFields(
fieldWithPath("[].steadyId").type(NUMBER).description("스테디 식별자"),
fieldWithPath("[].title").type(STRING).description("스테디 제목"),
fieldWithPath("[].type").type(STRING).description("스테디 타입"),
fieldWithPath("[].status").type(STRING).description("스테디 상태"),
fieldWithPath("[].deadline").type(STRING).description("마감일"),
fieldWithPath("[].viewCount").type(NUMBER).description("조회수"),
fieldWithPath("[].likeCount").type(NUMBER).description("좋아요 수")
)
))
.andExpect(status().isOk())
.andExpect(content().string(objectMapper.writeValueAsString(response)));
}

@Test
@DisplayName("검색 조건에 따른 전체 조회 결과를 반환한다.")
void getSteadiesByConditionTest() throws Exception {
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/dev/steady/steady/fixture/SteadyFixturesV2.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import dev.steady.steady.dto.response.SteadyQueryResponse;
import dev.steady.steady.dto.response.SteadyQuestionResponse;
import dev.steady.steady.dto.response.SteadyQuestionsResponse;
import dev.steady.steady.dto.response.SteadyRankResponse;
import dev.steady.user.domain.Position;
import dev.steady.user.domain.Stack;
import dev.steady.user.domain.User;
Expand All @@ -28,9 +29,11 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.IntStream;

import static dev.steady.steady.domain.ScheduledPeriod.FIVE_MONTH;
import static dev.steady.steady.domain.SteadyMode.ONLINE;
import static dev.steady.steady.domain.SteadyStatus.CLOSED;
import static dev.steady.steady.domain.SteadyType.STUDY;
import static dev.steady.user.fixture.UserFixturesV2.generateStackEntity;
import static org.instancio.Select.field;
Expand Down Expand Up @@ -80,6 +83,19 @@ public static Steady createSteady(User leader, List<Stack> stacks) {
.build();
}

public static List<Steady> createSteadiesWithLikeCount(User leader, List<Stack> stacks, int likeCount) {
return IntStream.rangeClosed(0, likeCount)
Copy link
Contributor

Choose a reason for hiding this comment

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

rangeClosed를 사용하여 마지막 값을 포함하신 이유가 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

마지막값을 포함한다는걸 좀 더 명확하게 하고싶어서 사용했습니다!

.mapToObj(count -> createSteadyWithLikeCount(leader, stacks, count))
.toList();
}

public static List<SteadyRankResponse> createSteadyRankResponses() {
return List.of(
new SteadyRankResponse(2L, "스테디 제목2", STUDY, CLOSED, LocalDate.of(2023, 12, 31), 10, 1),
new SteadyRankResponse(2L, "스테디 제목2", STUDY, CLOSED, LocalDate.of(2023, 12, 15), 10, 3)
);
}

public static Steady createSteadyWithStatus(User leader, List<Stack> stacks, SteadyStatus status) {
Steady steady = Steady.builder()
.name("테스트 스테디")
Expand Down Expand Up @@ -196,4 +212,10 @@ public static SliceResponse<MySteadyResponse> createMySteadyResponse() {
);
}

private static Steady createSteadyWithLikeCount(User leader, List<Stack> stacks, int likeCount) {
Steady steady = createSteady(leader, stacks);
ReflectionTestUtils.setField(steady, "likeCount", likeCount);
return steady;
}

}
Loading