Skip to content

Commit dea5223

Browse files
authored
[FEAT] 협업 광장 전체 조회 및 상세 조회 구현 (#35) (#58)
* [FEAT] 협업 광장 전체 조회 구현 * [FEAT] 협업 게시글 상세 조회 구현 * [FEAT] 모집 인원과 개최 인원 분리 로직 및 쿼리 작성
1 parent 0f148e3 commit dea5223

13 files changed

+597
-28
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package com.brainpix.joining.repository;
22

3+
import java.util.List;
4+
35
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Query;
7+
import org.springframework.data.repository.query.Param;
48

59
import com.brainpix.joining.entity.purchasing.CollectionGathering;
610

711
public interface CollectionGatheringRepository extends JpaRepository<CollectionGathering, Long> {
812

913
// 협업 횟수 조회 (승낙된 협업)
1014
Long countByJoinerIdAndAccepted(Long joinerId, Boolean accepted);
15+
16+
// 개최 인원 등록 정보 조회
17+
@Query("SELECT cg FROM CollectionGathering cg " +
18+
"JOIN FETCH cg.collaborationRecruitment cr " +
19+
"WHERE cr.parentCollaborationHub.id = :collaborationHubId " +
20+
"AND cg.initialGathering = true")
21+
List<CollectionGathering> findByCollaborationHubId(@Param("collaborationHubId") Long collaborationHubId);
1122
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.brainpix.post.controller;
2+
3+
import org.springframework.data.domain.Pageable;
4+
import org.springframework.http.ResponseEntity;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.PathVariable;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
import com.brainpix.api.ApiResponse;
11+
import com.brainpix.post.converter.GetCollaborationHubDetailDtoConverter;
12+
import com.brainpix.post.converter.GetCollaborationHubListDtoConverter;
13+
import com.brainpix.post.dto.GetCollaborationHubDetailDto;
14+
import com.brainpix.post.dto.GetCollaborationHubListDto;
15+
import com.brainpix.post.service.CollaborationHubService;
16+
17+
import lombok.RequiredArgsConstructor;
18+
19+
@RestController
20+
@RequestMapping("/collaborations")
21+
@RequiredArgsConstructor
22+
public class CollaborationHubController {
23+
24+
private final CollaborationHubService collaborationHubService;
25+
26+
@GetMapping
27+
public ResponseEntity<ApiResponse<GetCollaborationHubListDto.Response>> getCollaborationHubList(
28+
GetCollaborationHubListDto.Request request, Pageable pageable) {
29+
GetCollaborationHubListDto.Parameter parameter = GetCollaborationHubListDtoConverter.toParameter(request,
30+
pageable);
31+
GetCollaborationHubListDto.Response response = collaborationHubService.getCollaborationHubList(parameter);
32+
return ResponseEntity.ok(ApiResponse.success(response));
33+
}
34+
35+
@GetMapping("/{collaborationId}")
36+
public ResponseEntity<ApiResponse<GetCollaborationHubDetailDto.Response>> getCollaborationHubDetail(
37+
@PathVariable("collaborationId") Long collaborationId) {
38+
GetCollaborationHubDetailDto.Parameter parameter = GetCollaborationHubDetailDtoConverter.toParameter(
39+
collaborationId);
40+
GetCollaborationHubDetailDto.Response response = collaborationHubService.getCollaborationHubDetail(parameter);
41+
return ResponseEntity.ok(ApiResponse.success(response));
42+
}
43+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.brainpix.post.converter;
2+
3+
import java.time.LocalDateTime;
4+
import java.time.temporal.ChronoUnit;
5+
import java.util.List;
6+
7+
import com.brainpix.joining.entity.purchasing.CollectionGathering;
8+
import com.brainpix.post.dto.GetCollaborationHubDetailDto;
9+
import com.brainpix.post.entity.collaboration_hub.CollaborationHub;
10+
import com.brainpix.post.entity.collaboration_hub.CollaborationRecruitment;
11+
import com.brainpix.user.entity.Company;
12+
import com.brainpix.user.entity.User;
13+
14+
public class GetCollaborationHubDetailDtoConverter {
15+
16+
public static GetCollaborationHubDetailDto.Parameter toParameter(Long collaborationId) {
17+
return GetCollaborationHubDetailDto.Parameter.builder()
18+
.collaborationId(collaborationId)
19+
.build();
20+
}
21+
22+
public static GetCollaborationHubDetailDto.Response toResponse(CollaborationHub collaborationHub,
23+
List<CollectionGathering> collectionGathering, User writer,
24+
Long saveCount,
25+
Long totalIdeas, Long totalCollaborations) {
26+
27+
// 작성자
28+
GetCollaborationHubDetailDto.Writer writerDto = toWriter(writer, totalIdeas, totalCollaborations);
29+
30+
// 데드라인 계산
31+
LocalDateTime deadline = collaborationHub.getDeadline();
32+
LocalDateTime now = LocalDateTime.now();
33+
Long days = deadline.isBefore(now) ? 0L : ChronoUnit.DAYS.between(now, deadline);
34+
35+
// 모집 단위 (개최 인원에 속하지 않는 모집 단위만 필터링)
36+
List<GetCollaborationHubDetailDto.Recruitment> recruitments = collaborationHub.getCollaborations().stream()
37+
.filter(recruitment ->
38+
collectionGathering.stream()
39+
.noneMatch(gathering -> gathering.getCollaborationRecruitment().equals(recruitment))
40+
)
41+
.map(GetCollaborationHubDetailDtoConverter::toRecruitment)
42+
.toList();
43+
44+
// 개최 인원
45+
List<GetCollaborationHubDetailDto.OpenMember> openMembers = collectionGathering.stream()
46+
.map(GetCollaborationHubDetailDtoConverter::toOpenMember)
47+
.toList();
48+
49+
return GetCollaborationHubDetailDto.Response.builder()
50+
.collaborationId(collaborationHub.getId())
51+
.thumbnailImageUrl(collaborationHub.getImageList().get(0))
52+
.category(collaborationHub.getSpecialization().toString())
53+
.auth(collaborationHub.getPostAuth().toString())
54+
.title(collaborationHub.getTitle())
55+
.content(collaborationHub.getContent())
56+
.link(collaborationHub.getLink())
57+
.deadline(days)
58+
.viewCount(collaborationHub.getViewCount())
59+
.saveCount(saveCount)
60+
.createdDate(collaborationHub.getCreatedAt().toLocalDate())
61+
.writer(writerDto)
62+
.attachments(collaborationHub.getImageList())
63+
.recruitments(recruitments)
64+
.openMembers(openMembers)
65+
.build();
66+
}
67+
68+
public static GetCollaborationHubDetailDto.Writer toWriter(User writer, Long totalIdeas, Long totalCollaborations) {
69+
return GetCollaborationHubDetailDto.Writer.builder()
70+
.writerId(writer.getId())
71+
.name(writer.getName())
72+
.profileImageUrl(writer.getProfileImage())
73+
.role(writer instanceof Company ? "COMPANY" : "INDIVIDUAL")
74+
.specialization(writer.getProfile().getSpecializationList().get(0).toString())
75+
.totalIdeas(totalIdeas)
76+
.totalCollaborations(totalCollaborations)
77+
.build();
78+
}
79+
80+
public static GetCollaborationHubDetailDto.Recruitment toRecruitment(CollaborationRecruitment recruitment) {
81+
return GetCollaborationHubDetailDto.Recruitment.builder()
82+
.recruitmentId(recruitment.getId())
83+
.domain(recruitment.getDomain())
84+
.occupiedQuantity(recruitment.getGathering().getOccupiedQuantity())
85+
.totalQuantity(recruitment.getGathering().getTotalQuantity())
86+
.build();
87+
}
88+
89+
public static GetCollaborationHubDetailDto.OpenMember toOpenMember(CollectionGathering collectionGathering) {
90+
return GetCollaborationHubDetailDto.OpenMember.builder()
91+
.userId(collectionGathering.getJoiner().getId())
92+
.name(collectionGathering.getJoiner().getName())
93+
.domain(collectionGathering.getCollaborationRecruitment().getDomain())
94+
// .isOpenPortfolio(?)
95+
.build();
96+
}
97+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.brainpix.post.converter;
2+
3+
import java.time.LocalDateTime;
4+
import java.time.temporal.ChronoUnit;
5+
import java.util.List;
6+
7+
import org.springframework.data.domain.Page;
8+
import org.springframework.data.domain.Pageable;
9+
10+
import com.brainpix.api.code.error.CommonErrorCode;
11+
import com.brainpix.api.exception.BrainPixException;
12+
import com.brainpix.post.dto.GetCollaborationHubListDto;
13+
import com.brainpix.post.entity.collaboration_hub.CollaborationHub;
14+
import com.brainpix.post.enums.SortType;
15+
import com.brainpix.profile.entity.Specialization;
16+
17+
public class GetCollaborationHubListDtoConverter {
18+
public static GetCollaborationHubListDto.Parameter toParameter(GetCollaborationHubListDto.Request request,
19+
Pageable pageable) {
20+
21+
Specialization category = null;
22+
SortType sortType = null;
23+
24+
try {
25+
category =
26+
request.getCategory() != null ? Specialization.valueOf(request.getCategory().toUpperCase()) : null;
27+
sortType = request.getSortType() != null ?
28+
SortType.valueOf("COLLABORATION_" + request.getSortType().toUpperCase()) : null;
29+
} catch (Exception e) {
30+
throw new BrainPixException(CommonErrorCode.INVALID_PARAMETER);
31+
}
32+
33+
return GetCollaborationHubListDto.Parameter.builder()
34+
.keyword(request.getKeyword())
35+
.category(category)
36+
.onlyCompany(request.getOnlyCompany())
37+
.sortType(sortType)
38+
.pageable(pageable)
39+
.build();
40+
}
41+
42+
public static GetCollaborationHubListDto.Response toResponse(Page<Object[]> CollaborationHubs) {
43+
44+
List<GetCollaborationHubListDto.CollaborationDetail> collaborationDetailList = CollaborationHubs.stream()
45+
.map(CollaborationHub -> {
46+
CollaborationHub collaboration = (CollaborationHub)CollaborationHub[0]; // 실제 엔티티 객체
47+
Long saveCount = (Long)CollaborationHub[1]; // 저장 횟수
48+
LocalDateTime deadline = collaboration.getDeadline(); // 마감 기한
49+
LocalDateTime now = LocalDateTime.now(); // 현재 시간
50+
Long days = deadline.isBefore(now) ? 0L : ChronoUnit.DAYS.between(now, deadline); // D-DAY 계산
51+
52+
// 현재 인원 및 모집 인원
53+
Long occupiedQuantity = collaboration.getOccupiedQuantity();
54+
Long totalQuantity = collaboration.getTotalQuantity();
55+
return toCollaborationDetail(collaboration, saveCount, days, occupiedQuantity, totalQuantity);
56+
}
57+
).toList();
58+
59+
return GetCollaborationHubListDto.Response.builder()
60+
.collaborationDetailList(collaborationDetailList)
61+
.totalPages(CollaborationHubs.getTotalPages())
62+
.totalElements((int)CollaborationHubs.getTotalElements())
63+
.currentPage(CollaborationHubs.getNumber())
64+
.currentSize(CollaborationHubs.getNumberOfElements())
65+
.hasNext(CollaborationHubs.hasNext())
66+
.build();
67+
}
68+
69+
public static GetCollaborationHubListDto.CollaborationDetail toCollaborationDetail(
70+
CollaborationHub CollaborationHub, Long saveCount,
71+
Long deadline, Long occupiedQuantity, Long totalQuantity) {
72+
return GetCollaborationHubListDto.CollaborationDetail.builder()
73+
.collaborationId(CollaborationHub.getId())
74+
.auth(CollaborationHub.getPostAuth().toString())
75+
.writerImageUrl(CollaborationHub.getWriter().getProfileImage())
76+
.writerName(CollaborationHub.getWriter().getName())
77+
.thumbnailImageUrl(CollaborationHub.getImageList().get(0))
78+
.title(CollaborationHub.getTitle())
79+
.deadline(deadline)
80+
.category(CollaborationHub.getSpecialization().toString())
81+
.occupiedQuantity(occupiedQuantity)
82+
.totalQuantity(totalQuantity)
83+
.saveCount(saveCount)
84+
.viewCount(CollaborationHub.getViewCount())
85+
.build();
86+
}
87+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.brainpix.post.dto;
2+
3+
import java.time.LocalDate;
4+
import java.util.List;
5+
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
9+
public class GetCollaborationHubDetailDto {
10+
11+
@Builder
12+
@Getter
13+
public static class Parameter {
14+
private Long collaborationId; // 협업 게시글 ID
15+
}
16+
17+
@Builder
18+
@Getter
19+
public static class Response {
20+
private Long collaborationId; // 협업 게시글 ID
21+
private String thumbnailImageUrl; // 썸네일 이미지 URL
22+
private String category; // 협업 게시글 카테고리
23+
private String auth; // 공개 유형 (ALL, COMPANY)
24+
private String title; // 협업 게시글 제목
25+
private String content; // 협업 게시글 내용
26+
private String link; // 링크
27+
private Long deadline; // 남은 기간
28+
private Long viewCount; // 협업 게시글 조회수
29+
private Long saveCount; // 협업 게시글 저장수
30+
private LocalDate createdDate; // 협업 게시글 작성일 (YYYY/MM/DD)
31+
private Writer writer; // 작성자
32+
private List<String> attachments; // 첨부 파일 목록
33+
private List<Recruitment> recruitments; // 모집 단위
34+
private List<OpenMember> openMembers; // 개최 인원
35+
}
36+
37+
@Builder
38+
@Getter
39+
public static class Writer {
40+
private Long writerId; // 작성자의 식별자 값
41+
private String name; // 작성자 이름
42+
private String profileImageUrl; // 작성자 프로필 이미지 URL
43+
private String role; // 작성자 역할 (COMPANY, INDIVIDUAL)
44+
private String specialization; // 작성자의 분야 (IT_TECH, DESIGN, ...)
45+
private Long totalIdeas; // 작성자가 등록한 협업 게시글 수
46+
private Long totalCollaborations; // 작성자가 협업한 경험 수
47+
}
48+
49+
@Builder
50+
@Getter
51+
public static class Recruitment {
52+
private Long recruitmentId; // 모집 단위 식별자
53+
private String domain; // 모집 분야
54+
private Long occupiedQuantity; // 현재 인원
55+
private Long totalQuantity; // 전체 인원
56+
}
57+
58+
@Builder
59+
@Getter
60+
public static class OpenMember {
61+
private Long userId; // 유저 식별자 값
62+
private String name; // 유저 이름
63+
private String domain; // 유저 역할
64+
private Boolean isOpenPortfolio; // 포트폴리오 불러오기 여부
65+
}
66+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.brainpix.post.dto;
2+
3+
import java.util.List;
4+
5+
import org.springframework.data.domain.Pageable;
6+
7+
import com.brainpix.post.enums.SortType;
8+
import com.brainpix.profile.entity.Specialization;
9+
10+
import lombok.Builder;
11+
import lombok.Getter;
12+
import lombok.NoArgsConstructor;
13+
import lombok.Setter;
14+
15+
public class GetCollaborationHubListDto {
16+
17+
@NoArgsConstructor
18+
@Getter
19+
@Setter
20+
public static class Request {
21+
private String keyword; // 검색 키워드
22+
private String category; // 카테고리
23+
private Boolean onlyCompany; // 기업 공개 제외/기업 공개만 보기
24+
private String sortType; // 정렬 기준
25+
}
26+
27+
@Builder
28+
@Getter
29+
public static class Parameter {
30+
private String keyword; // 검색 키워드
31+
private Specialization category; // 카테고리
32+
private Boolean onlyCompany; // 기업 공개 제외/기업 공개만 보기
33+
private SortType sortType; // 정렬 기준
34+
private Pageable pageable; // 페이징 기준
35+
}
36+
37+
@Builder
38+
@Getter
39+
public static class Response {
40+
private List<CollaborationDetail> collaborationDetailList; // 결과 값 리스트
41+
private Integer totalPages; // 전체 페이지 수
42+
private Integer totalElements; // 전체 결과의 크기
43+
private Integer currentPage; // 현재 페이지 수
44+
private Integer currentSize; // 현재 페이지의 크기
45+
private Boolean hasNext; // 다음 페이지 존재 여부
46+
}
47+
48+
@Builder
49+
@Getter
50+
public static class CollaborationDetail {
51+
private Long collaborationId; // 게시글의 식별자 값
52+
private String auth; // 공개 범위 (ALL, COMPANY)
53+
private String writerImageUrl; // 작성자 프로필 이미지 경로
54+
private String writerName; // 작성자 닉네임
55+
private String thumbnailImageUrl; // 대표 이미지 경로
56+
private String title; // 아이디어 제목
57+
private Long deadline; // 남은 기간
58+
private String category; // 게시글의 카테고리
59+
private Long occupiedQuantity; // 현재 인원
60+
private Long totalQuantity; // 전체 인원
61+
private Long saveCount; // 저장수
62+
private Long viewCount; // 조회수
63+
}
64+
}

0 commit comments

Comments
 (0)