Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class CreateStudyService {
public void createStudy(CreateStudyRequest request, Long leaderId, MultipartFile imageFile) {
long studyId = idGenerator.nextId();
Study study = Study.of(studyId, leaderId, request.name(), request.maxMembers(),
Fee.of(request.hasFee(), request.amount()), request.description());
Fee.of(request.hasFee(), request.amount()), null, request.description(), request.isOnline());
StudyStats studyStats = StudyStats.of(studyId);
StudyMember studyMember = StudyMember.create(idGenerator.nextId(), studyId, leaderId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package kr.spot.study.application.mapper;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import kr.spot.study.domain.Study;
import kr.spot.study.presentation.query.dto.response.GetStudyOverviewResponse;
import kr.spot.study.presentation.query.dto.response.GetStudyOverviewResponse.StudyOverview;

public class StudyDTOMapper {

public static GetStudyOverviewResponse toDTO(List<Study> studies, boolean hasNext,
public static GetStudyOverviewResponse toDTO(
List<Study> studies,
Set<Long> likedStudyIds,
boolean hasNext,
Long nextCursor,
Long totalElements) {
Long totalElements
) {
Set<Long> safelikedIds = likedStudyIds != null ? likedStudyIds : Collections.emptySet();

List<StudyOverview> list = studies.stream().map(
study -> StudyOverview.of(
study.getId(),
Expand All @@ -18,12 +26,21 @@ public static GetStudyOverviewResponse toDTO(List<Study> studies, boolean hasNex
study.getMaxMembers(),
study.getCurrentMembers(),
0,
false,
safelikedIds.contains(study.getId()),
0,
study.getImageUrl()
)
).toList();

return GetStudyOverviewResponse.of(list, hasNext, nextCursor, totalElements);
}

public static GetStudyOverviewResponse toDTO(
List<Study> studies,
boolean hasNext,
Long nextCursor,
Long totalElements
) {
return toDTO(studies, Collections.emptySet(), hasNext, nextCursor, totalElements);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package kr.spot.study.application.query;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import kr.spot.ports.GetPreferredCategoryPort;
import kr.spot.ports.GetPreferredRegionPort;
import kr.spot.study.application.mapper.StudyDTOMapper;
Expand All @@ -10,6 +13,7 @@
import kr.spot.study.domain.enums.RecruitingStatus;
import kr.spot.study.domain.enums.SortBy;
import kr.spot.study.domain.enums.StudyMemberStatus;
import kr.spot.study.infrastructure.jpa.associations.StudyLikeRepository;
import kr.spot.study.infrastructure.jpa.querydsl.StudyQueryRepository;
import kr.spot.study.presentation.query.dto.response.GetStudyOverviewResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -26,9 +30,10 @@ public class GetMyStudyInfoService {
private final GetPreferredRegionPort getPreferredRegionPort;
private final GetPreferredCategoryPort getPreferredCategoryPort;
private final StudyQueryRepository studyQueryRepository;
private final StudyLikeRepository studyLikeRepository;

public GetStudyOverviewResponse getMyStudyOverview(
Long viewerId,
long viewerId,
StudyMemberStatus status,
Long cursor,
int size
Expand All @@ -45,83 +50,196 @@ public GetStudyOverviewResponse getMyStudyOverview(
viewerId,
status
);
return toCursorPage(rows, pageSize, totalElements);
return toCursorPage(rows, viewerId, pageSize, totalElements);
}

public GetStudyOverviewResponse getMyPreferredRegionStudies(
Long viewerId,
long viewerId,
RecruitingStatus recruitingStatus,
FeeCategory feeCategory,
List<Category> categories,
Boolean isOnline,
SortBy sortBy,
Long cursor,
Integer size,
int size,
List<String> regionCodes
) {
final int pageSize = Math.min(size, MAX_PAGE_SIZE);
List<String> preferredRegionCodes = getPreferredRegionPort.get(viewerId);
List<String> filteredRegionCodes = filterPreferredRegionCodes(regionCodes, preferredRegionCodes);

List<Study> rows = studyQueryRepository.findMyPreferredRegionStudies(
recruitingStatus,
feeCategory,
categories,
isOnline,
sortBy,
cursor,
pageSize + 1,
filterPreferredRegionCodes(regionCodes, preferredRegionCodes)
filteredRegionCodes
);

long totalElements = studyQueryRepository.countMyPreferredRegionStudies(
recruitingStatus,
feeCategory,
categories,
filterPreferredRegionCodes(regionCodes, preferredRegionCodes)
isOnline,
filteredRegionCodes
);
return toCursorPage(rows, pageSize, totalElements);
return toCursorPage(rows, viewerId, pageSize, totalElements);
}

public GetStudyOverviewResponse getMyPreferredCategoryStudies(
Long viewerId,
long viewerId,
Category category,
RecruitingStatus recruitingStatus,
FeeCategory feeCategory,
Boolean isOnline,
SortBy sortBy,
Long cursor,
Integer size
int size
) {
final int pageSize = Math.min(size, MAX_PAGE_SIZE);
List<Category> preferredRegionCodes = getCategories(viewerId, category);
List<Category> preferredCategories = getCategories(viewerId, category);

List<Study> rows = studyQueryRepository.findMyPreferredCategoryStudies(
recruitingStatus,
feeCategory,
isOnline,
sortBy,
cursor,
pageSize + 1,
preferredRegionCodes
preferredCategories
);

long totalElements = studyQueryRepository.countMyPreferredCategoryStudies(
recruitingStatus,
feeCategory,
preferredRegionCodes
isOnline,
preferredCategories
);
return toCursorPage(rows, viewerId, pageSize, totalElements);
}

public GetStudyOverviewResponse getRecruitingStudies(
long viewerId,
FeeCategory feeCategory,
List<Category> categories,
Boolean isOnline,
SortBy sortBy,
Long cursor,
int size
) {
final int pageSize = Math.min(size, MAX_PAGE_SIZE);

List<Study> rows = studyQueryRepository.findRecruitingStudies(
feeCategory,
categories,
isOnline,
sortBy,
cursor,
pageSize + 1
);

long totalElements = studyQueryRepository.countRecruitingStudies(
feeCategory,
categories,
isOnline
);
return toCursorPage(rows, viewerId, pageSize, totalElements);
}

public GetStudyOverviewResponse getStudiesByCategory(
long viewerId,
RecruitingStatus recruitingStatus,
FeeCategory feeCategory,
Category category,
Boolean isOnline,
SortBy sortBy,
Long cursor,
int size
) {
final int pageSize = Math.min(size, MAX_PAGE_SIZE);

List<Study> rows = studyQueryRepository.findStudiesByCategory(
recruitingStatus,
feeCategory,
category,
isOnline,
sortBy,
cursor,
pageSize + 1
);
return toCursorPage(rows, pageSize, totalElements);

long totalElements = studyQueryRepository.countStudiesByCategory(
recruitingStatus,
feeCategory,
category,
isOnline
);
return toCursorPage(rows, viewerId, pageSize, totalElements);
}

public GetStudyOverviewResponse getLikedStudies(long memberId, Long cursor, int size) {
public GetStudyOverviewResponse getLikedStudies(long viewerId, Long cursor, int size) {
final int pageSize = Math.min(size, MAX_PAGE_SIZE);
List<Study> rows = studyQueryRepository.findLikedStudies(memberId, cursor, pageSize + 1);
long totalElements = studyQueryRepository.countLikedStudies(memberId);
return toCursorPage(rows, pageSize, totalElements);
List<Study> rows = studyQueryRepository.findLikedStudies(viewerId, cursor, pageSize + 1);
long totalElements = studyQueryRepository.countLikedStudies(viewerId);
return toCursorPage(rows, viewerId, pageSize, totalElements);
}

public GetStudyOverviewResponse getRecommendedStudies(long viewerId) {
final int recommendCount = 3;

List<Category> preferredCategories = getPreferredCategories(viewerId);
List<Study> result = pickRandomStudiesFromPreferred(preferredCategories, recommendCount);
fillWithPopularStudies(result, recommendCount);

Set<Long> likedStudyIds = studyLikeRepository.findStudyIdsByMemberId(viewerId);
return StudyDTOMapper.toDTO(result, likedStudyIds, false, null, (long) result.size());
}

private List<Category> getPreferredCategories(long memberId) {
return getPreferredCategoryPort.get(memberId)
.stream()
.map(Category::fromString)
.toList();
}

private List<Study> pickRandomStudiesFromPreferred(List<Category> categories, int count) {
if (categories.isEmpty()) {
return new ArrayList<>();
}

final int fetchLimit = 20;
List<Study> candidates = studyQueryRepository.findRecruitingStudiesByCategories(
categories, fetchLimit);

List<Study> shuffled = new ArrayList<>(candidates);
Collections.shuffle(shuffled);

return new ArrayList<>(shuffled.stream().limit(count).toList());
}

private void fillWithPopularStudies(List<Study> result, int targetCount) {
if (result.size() >= targetCount) {
return;
}

List<Long> excludeIds = result.stream().map(Study::getId).toList();
int remaining = targetCount - result.size();

List<Study> popularStudies = studyQueryRepository.findPopularRecruitingStudies(
excludeIds, remaining);
result.addAll(popularStudies);
}

private GetStudyOverviewResponse toCursorPage(List<Study> rows, int pageSize,
Long totalElements) {
private GetStudyOverviewResponse toCursorPage(List<Study> rows, long viewerId, int pageSize,
long totalElements) {
boolean hasNext = rows.size() > pageSize;
List<Study> pageContent = hasNext ? rows.subList(0, pageSize) : rows;
Long nextCursor = hasNext ? pageContent.getLast().getId() : null;
return StudyDTOMapper.toDTO(pageContent, hasNext, nextCursor, totalElements);
Set<Long> likedStudyIds = studyLikeRepository.findStudyIdsByMemberId(viewerId);
return StudyDTOMapper.toDTO(pageContent, likedStudyIds, hasNext, nextCursor, totalElements);
}

private List<String> filterPreferredRegionCodes(List<String> regionCodes,
Expand Down
12 changes: 10 additions & 2 deletions modules/study/src/main/java/kr/spot/study/domain/Study.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,25 @@ public class Study extends BaseEntity {
@Enumerated(EnumType.STRING)
private RecruitingStatus recruitingStatus;

@Column(nullable = false)
private Boolean isOnline = false;

public static Study of(Long id, Long leaderId, String name, Integer maxMembers, Fee fee,
String description) {
return of(id, leaderId, name, maxMembers, fee, null, description);
return of(id, leaderId, name, maxMembers, fee, null, description, false);
}

public static Study of(Long id, Long leaderId, String name, Integer maxMembers, Fee fee,
String imageUrl, String description) {
return of(id, leaderId, name, maxMembers, fee, imageUrl, description, false);
}

public static Study of(Long id, Long leaderId, String name, Integer maxMembers, Fee fee,
String imageUrl, String description, Boolean isOnline) {
validateStudyNameIsNotBlank(name);
validateMaxMembers(maxMembers);
return new Study(id, leaderId, name, maxMembers, CURRENT_MEMBERS, fee, imageUrl, description,
RecruitingStatus.RECRUITING);
RecruitingStatus.RECRUITING, isOnline != null && isOnline);
}

private static void validateStudyNameIsNotBlank(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ int hardDelete(@Param("studyId") long studyId,
@Modifying
@Query(value = "DELETE FROM study_like WHERE member_id = :memberId", nativeQuery = true)
void deleteAllByMemberId(@Param("memberId") long memberId);

@Query("SELECT sl.studyId FROM StudyLike sl WHERE sl.memberId = :memberId")
java.util.Set<Long> findStudyIdsByMemberId(@Param("memberId") long memberId);
}
Loading