Skip to content

Commit f28a12d

Browse files
authored
Merge pull request #199 from prgrms-web-devcourse-final-project/feature/EA3-167-study-detail
[EA3-167] feature: 스터디 조회 기능 구현
2 parents 9729075 + 64cbb0a commit f28a12d

File tree

6 files changed

+134
-23
lines changed

6 files changed

+134
-23
lines changed

src/main/java/grep/neogulcoder/domain/study/controller/StudyController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public ApiResponse<StudyHeaderResponse> getStudyHeader(@PathVariable("studyId")
4646

4747
@GetMapping("/{studyId}")
4848
public ApiResponse<StudyResponse> getStudy(@PathVariable("studyId") Long studyId) {
49-
return ApiResponse.success(new StudyResponse());
49+
return ApiResponse.success(studyService.getStudy(studyId));
5050
}
5151

5252
@GetMapping("/me/images")

src/main/java/grep/neogulcoder/domain/study/controller/dto/response/StudyResponse.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package grep.neogulcoder.domain.study.controller.dto.response;
22

3-
import grep.neogulcoder.domain.attendance.controller.dto.response.AttendanceResponse;
43
import grep.neogulcoder.domain.calender.controller.dto.response.TeamCalendarResponse;
5-
import grep.neogulcoder.domain.studypost.controller.dto.StudyPostListResponse;
4+
import grep.neogulcoder.domain.study.Study;
5+
import grep.neogulcoder.domain.studypost.controller.dto.response.FreePostInfo;
6+
import grep.neogulcoder.domain.studypost.controller.dto.response.NoticePostInfo;
67
import io.swagger.v3.oas.annotations.media.Schema;
8+
import lombok.Builder;
79
import lombok.Getter;
810

911
import java.util.List;
@@ -26,15 +28,39 @@ public class StudyResponse {
2628
@Schema(description = "스터디 총 게시물 수", example = "10")
2729
private int totalPostCount;
2830

29-
@Schema(description = "출석 정보")
30-
private List<AttendanceResponse> attendances;
31+
@Schema(description = "팀 달력")
32+
private List<TeamCalendarResponse> teamCalendars;
3133

32-
@Schema(description = "출석률", example = "60")
33-
private int attendanceRate;
34+
@Schema(description = "스터디 공지글 2개")
35+
private List<NoticePostInfo> noticePosts;
3436

35-
@Schema(description = "팀 달력")
36-
private List<TeamCalendarResponse> teamCalenders;
37+
@Schema(description = "스터디 자유글 3개")
38+
private List<FreePostInfo> freePosts;
39+
40+
@Builder
41+
private StudyResponse(int progressDays, int totalDays, int capacity, int currentCount, int totalPostCount,
42+
List<TeamCalendarResponse> teamCalendars, List<NoticePostInfo> noticePosts, List<FreePostInfo> freePosts) {
43+
this.progressDays = progressDays;
44+
this.totalDays = totalDays;
45+
this.capacity = capacity;
46+
this.currentCount = currentCount;
47+
this.totalPostCount = totalPostCount;
48+
this.teamCalendars = teamCalendars;
49+
this.noticePosts = noticePosts;
50+
this.freePosts = freePosts;
51+
}
3752

38-
@Schema(description = "게시글 리스트")
39-
private List<StudyPostListResponse> studyPosts;
53+
public static StudyResponse from(Study study, int progressDays, int totalDays, int totalPostCount,
54+
List<TeamCalendarResponse> teamCalendars, List<NoticePostInfo> noticePosts, List<FreePostInfo> freePosts) {
55+
return StudyResponse.builder()
56+
.progressDays(progressDays)
57+
.totalDays(totalDays)
58+
.capacity(study.getCapacity())
59+
.currentCount(study.getCurrentCount())
60+
.totalPostCount(totalPostCount)
61+
.teamCalendars(teamCalendars)
62+
.noticePosts(noticePosts)
63+
.freePosts(freePosts)
64+
.build();
65+
}
4066
}

src/main/java/grep/neogulcoder/domain/study/service/StudyService.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package grep.neogulcoder.domain.study.service;
22

3+
import grep.neogulcoder.domain.calender.controller.dto.response.TeamCalendarResponse;
4+
import grep.neogulcoder.domain.calender.service.TeamCalendarService;
35
import grep.neogulcoder.domain.recruitment.post.repository.RecruitmentPostRepository;
46
import grep.neogulcoder.domain.study.Study;
57
import grep.neogulcoder.domain.study.StudyMember;
@@ -12,6 +14,10 @@
1214
import grep.neogulcoder.domain.study.repository.StudyMemberRepository;
1315
import grep.neogulcoder.domain.study.repository.StudyQueryRepository;
1416
import grep.neogulcoder.domain.study.repository.StudyRepository;
17+
import grep.neogulcoder.domain.studypost.controller.dto.response.FreePostInfo;
18+
import grep.neogulcoder.domain.studypost.controller.dto.response.NoticePostInfo;
19+
import grep.neogulcoder.domain.studypost.repository.StudyPostQueryRepository;
20+
import grep.neogulcoder.domain.studypost.repository.StudyPostRepository;
1521
import grep.neogulcoder.domain.users.entity.User;
1622
import grep.neogulcoder.domain.users.repository.UserRepository;
1723
import grep.neogulcoder.global.exception.business.BusinessException;
@@ -27,6 +33,8 @@
2733
import org.springframework.web.multipart.MultipartFile;
2834

2935
import java.io.IOException;
36+
import java.time.LocalDate;
37+
import java.time.temporal.ChronoUnit;
3038
import java.util.List;
3139
import java.util.Optional;
3240

@@ -46,6 +54,9 @@ public class StudyService {
4654
private final RecruitmentPostRepository recruitmentPostRepository;
4755
private final StudyMemberQueryRepository studyMemberQueryRepository;
4856
private final UserRepository userRepository;
57+
private final StudyPostRepository studyPostRepository;
58+
private final StudyPostQueryRepository studyPostQueryRepository;
59+
private final TeamCalendarService teamCalendarService;
4960

5061
public StudyItemPagingResponse getMyStudiesPaging(Pageable pageable, Long userId, Boolean finished) {
5162
Page<StudyItemResponse> page = studyQueryRepository.findMyStudiesPaging(pageable, userId, finished);
@@ -62,12 +73,30 @@ public List<StudyItemResponse> getMyStudies(Long userId) {
6273
}
6374

6475
public StudyHeaderResponse getStudyHeader(Long studyId) {
65-
Study study = studyRepository.findByIdAndActivatedTrue(studyId)
66-
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
76+
Study study = findValidStudy(studyId);
6777

6878
return StudyHeaderResponse.from(study);
6979
}
7080

81+
public StudyResponse getStudy(Long studyId) {
82+
Study study = findValidStudy(studyId);
83+
84+
int progressDays = (int) ChronoUnit.DAYS.between(study.getStartDate().toLocalDate(), LocalDate.now()) + 1;
85+
int totalDays = (int) ChronoUnit.DAYS.between(study.getStartDate().toLocalDate(), study.getEndDate().toLocalDate()) + 1;
86+
progressDays = Math.max(0, Math.min(progressDays, totalDays));
87+
int totalPostCount = studyPostRepository.countByStudyIdAndActivatedTrue(studyId);
88+
89+
LocalDate now = LocalDate.now();
90+
int currentYear = now.getYear();
91+
int currentMonth = now.getMonthValue();
92+
List<TeamCalendarResponse> teamCalendars = teamCalendarService.findByMonth(studyId, currentYear, currentMonth);
93+
94+
List<NoticePostInfo> noticePosts = studyPostQueryRepository.findLatestNoticeInfoBy(studyId);
95+
List<FreePostInfo> freePosts = studyPostQueryRepository.findLatestFreeInfoBy(studyId);
96+
97+
return StudyResponse.from(study, progressDays, totalDays, totalPostCount, teamCalendars, noticePosts, freePosts);
98+
}
99+
71100
public List<StudyImageResponse> getStudyImages(Long userId) {
72101
List<Study> myStudiesImage = studyMemberRepository.findStudiesByUserId(userId);
73102

@@ -77,8 +106,7 @@ public List<StudyImageResponse> getStudyImages(Long userId) {
77106
}
78107

79108
public StudyInfoResponse getMyStudyContent(Long studyId, Long userId) {
80-
Study study = studyRepository.findByIdAndActivatedTrue(studyId)
81-
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
109+
Study study = findValidStudy(studyId);
82110

83111
validateStudyMember(studyId, userId);
84112
validateStudyLeader(studyId, userId);
@@ -118,8 +146,7 @@ public Long createStudy(StudyCreateRequest request, Long userId, MultipartFile i
118146

119147
@Transactional
120148
public void updateStudy(Long studyId, StudyUpdateRequest request, Long userId, MultipartFile image) throws IOException {
121-
Study study = studyRepository.findById(studyId)
122-
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
149+
Study study = findValidStudy(studyId);
123150

124151
validateLocation(request.getStudyType(), request.getLocation());
125152
validateStudyMember(studyId, userId);
@@ -142,8 +169,7 @@ public void updateStudy(Long studyId, StudyUpdateRequest request, Long userId, M
142169

143170
@Transactional
144171
public void deleteStudy(Long studyId, Long userId) {
145-
Study study = studyRepository.findById(studyId)
146-
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
172+
Study study = findValidStudy(studyId);
147173

148174
validateStudyMember(studyId, userId);
149175
validateStudyLeader(studyId, userId);
@@ -156,12 +182,16 @@ public void deleteStudy(Long studyId, Long userId) {
156182

157183
@Transactional
158184
public void deleteStudyByAdmin(Long studyId) {
159-
Study study = studyRepository.findById(studyId)
160-
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
185+
Study study = findValidStudy(studyId);
161186

162187
study.delete();
163188
}
164189

190+
private Study findValidStudy(Long studyId) {
191+
return studyRepository.findById(studyId)
192+
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
193+
}
194+
165195
private static void validateLocation(StudyType studyType, String location) {
166196
if ((studyType == StudyType.OFFLINE || studyType == StudyType.HYBRID) && (location == null || location.isBlank())) {
167197
throw new BusinessException(STUDY_LOCATION_REQUIRED);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package grep.neogulcoder.domain.studypost.controller.dto.response;
2+
3+
import com.querydsl.core.annotations.QueryProjection;
4+
import grep.neogulcoder.domain.studypost.Category;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Getter;
7+
8+
import java.time.LocalDate;
9+
import java.time.LocalDateTime;
10+
11+
@Getter
12+
public class FreePostInfo {
13+
14+
@Schema(example = "3", description = "게시글 ID")
15+
private long postId;
16+
17+
@Schema(example = "공지", description = "게시글 타입")
18+
private String category;
19+
20+
@Schema(example = "제목", description = "공지글 제목")
21+
private String title;
22+
23+
@Schema(example = "2025-07-21", description = "생성일")
24+
private LocalDate createdAt;
25+
26+
@QueryProjection
27+
public FreePostInfo(long postId, Category category, String title, LocalDateTime createdAt) {
28+
this.postId = postId;
29+
this.category = category.getKorean();
30+
this.title = title;
31+
this.createdAt = createdAt.toLocalDate();
32+
}
33+
}

src/main/java/grep/neogulcoder/domain/studypost/repository/StudyPostQueryRepository.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import java.util.Optional;
2323
import java.util.function.Supplier;
2424

25-
import static grep.neogulcoder.domain.studypost.Category.NOTICE;
25+
import static grep.neogulcoder.domain.studypost.Category.*;
2626
import static grep.neogulcoder.domain.studypost.StudyPostErrorCode.NOT_VALID_CONDITION;
2727
import static grep.neogulcoder.domain.studypost.comment.QStudyPostComment.studyPostComment;
2828
import static grep.neogulcoder.domain.users.entity.QUser.user;
@@ -34,6 +34,7 @@ public class StudyPostQueryRepository {
3434
private final JPAQueryFactory queryFactory;
3535

3636
public static final int NOTICE_POST_LIMIT = 2;
37+
public static final int FREE_POST_LIMIT = 3;
3738

3839
private final QStudyPost studyPost = QStudyPost.studyPost;
3940

@@ -139,6 +140,26 @@ public List<NoticePostInfo> findLatestNoticeInfoBy(Long studyId) {
139140
.fetch();
140141
}
141142

143+
public List<FreePostInfo> findLatestFreeInfoBy(Long studyId) {
144+
return queryFactory.select(
145+
new QFreePostInfo(
146+
studyPost.id,
147+
studyPost.category,
148+
studyPost.title,
149+
studyPost.createdDate
150+
)
151+
)
152+
.from(studyPost)
153+
.where(
154+
studyPost.activated.isTrue(),
155+
studyPost.study.id.eq(studyId),
156+
studyPost.category.eq(FREE)
157+
)
158+
.orderBy(studyPost.createdDate.desc())
159+
.limit(FREE_POST_LIMIT)
160+
.fetch();
161+
}
162+
142163
private OrderSpecifier<?> resolveOrderSpecifier(String attributeName, Boolean isAsc) {
143164
if (attributeName == null || isAsc == null) {
144165
return null;

src/main/java/grep/neogulcoder/domain/studypost/repository/StudyPostRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
import grep.neogulcoder.domain.studypost.StudyPost;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6-
public interface StudyPostRepository extends JpaRepository<StudyPost,Long> {
6+
public interface StudyPostRepository extends JpaRepository<StudyPost, Long> {
7+
int countByStudyIdAndActivatedTrue(Long studyId);
78
}

0 commit comments

Comments
 (0)