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: 공동 모임장 구현 #449

Merged
merged 26 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
678fa38
add: 공동 모임장 관련 엔티티 및 레포지토리 추가
mikekks Oct 9, 2024
9edea2a
chore: 모임 생성 API에 공동 모임장 관련 로직 추가
mikekks Oct 9, 2024
d5b21c6
chore: 모임 수정 API에 공동 모임장 관련 로직 추가
mikekks Oct 9, 2024
3e692bc
chore: 모임 삭제 API에 공동 모임장 관련 로직 추가
mikekks Oct 9, 2024
057ca35
chore: id -> ManyToOne 연관관계 생성
mikekks Oct 9, 2024
ed822a5
chore: 연관관계 설정으로 인한 수정
mikekks Oct 9, 2024
20c48d3
chore: join 컬럼 이름 변경
mikekks Oct 9, 2024
089003a
chore: 모임 상세 조회 API에 공동 모임장 관련 로직 추가
mikekks Oct 9, 2024
d1f6943
fix: 기존 검증로직 원상 복구 및 공동모임장 설정하지 않은 경우 분기처리
mikekks Oct 9, 2024
12f1b57
add: 공동 모임장 관련 테이블 추가
mikekks Oct 9, 2024
b80d4c3
test: 공동 모임장 관련 테스트 코드 추가
mikekks Oct 9, 2024
c8fc9fd
chore: 메서드 이름 변경
mikekks Oct 9, 2024
63b99af
feat: JointLeaders 일급컬렉션 구현 및 공동모임장 여부 확인 로직 구현
mikekks Oct 12, 2024
605c3d0
feat: 모임 상세 조회 API 에 공동모임장 여부 구현
mikekks Oct 12, 2024
bbcc00f
chore: JointLeader 자료형 Map 으로 변경 및 hasNotJointLeader 추가
mikekks Oct 12, 2024
0467d51
chore: nullable 하게 변경
mikekks Oct 12, 2024
a492c6c
feat: 내 모임 조회 API 에 공동모임장 여부 추가
mikekks Oct 12, 2024
00549e3
fix: isJointLeader 호환을 위해 getter 메서드 직접 구현
mikekks Oct 12, 2024
dc7142a
chore: JointLeader -> CoLeader로 수정
mikekks Oct 12, 2024
d7107e7
chore: CoLeaders 조건 반전
mikekks Oct 12, 2024
825de21
chore: @Getter(AccessLevel.NONE) 추가
mikekks Oct 12, 2024
91f6cc5
chore: update 메서드 수정
mikekks Oct 12, 2024
bf730ef
chore: 필드 설명 추가
mikekks Oct 12, 2024
1eb5d33
chore: 모임 생성 코드 리뷰 반영
mikekks Oct 12, 2024
20005c9
feat: 공동 모임장이 존재하지 않는 경우의 예외 처리
mikekks Oct 13, 2024
48fd576
merge: 충돌 해결
mikekks Oct 13, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.sopt.makers.crew.main.entity.meeting;

import org.sopt.makers.crew.main.entity.user.User;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "co_leader")
public class CoLeader {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "meetingId")
@NotNull
private Meeting meeting;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "userId")
@NotNull
private User user;

@Builder
private CoLeader(Meeting meeting, User user) {
this.meeting = meeting;
this.user = user;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.sopt.makers.crew.main.entity.meeting;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CoLeaderRepository extends JpaRepository<CoLeader, Integer> {

void deleteAllByMeetingId(Integer meetingId);

List<CoLeader> findAllByMeetingId(Integer meetingId);

List<CoLeader> findAllByMeetingIdIn(List<Integer> meetingId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.sopt.makers.crew.main.entity.meeting;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CoLeaders {
/**
* Key : MeetingId
* Value : 해당 모임의 공동 모임장 목록
*
* @implNote : List 내에 있는 CoLeader 객체는 fetch join 으로 다른 객체를 불러오지 않은 상태
* @implNote : 해당 자료형을 사용할 때는 'hasCoLeader' 메서드 사용 적극 권장
*
* */
private final Map<Integer, List<CoLeader>> coLeadersMap;

public CoLeaders(List<CoLeader> coLeaders) {
this.coLeadersMap = coLeaders.stream()
.collect(Collectors.groupingBy(coLeader -> coLeader.getMeeting().getId()));
}

public boolean isCoLeader(Integer meetingId, Integer requestUserId) {
if (!isCoLeaderPresent(meetingId)) {
return false;
}

return coLeadersMap.get(meetingId).stream()
.anyMatch(coLeader -> coLeader.getUser().getId().equals(requestUserId));
}

public List<CoLeader> getCoLeaders(Integer meetingId) {
if (!isCoLeaderPresent(meetingId)) {
return Collections.emptyList();
}

return Collections.unmodifiableList(coLeadersMap.get(meetingId));
}

private boolean isCoLeaderPresent(Integer meetingId) {
return coLeadersMap.containsKey(meetingId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import static org.sopt.makers.crew.main.global.exception.ErrorStatus.*;

import java.util.List;
import java.util.Optional;

import org.sopt.makers.crew.main.global.exception.NotFoundException;
import org.sopt.makers.crew.main.global.exception.UnAuthorizedException;
import org.springframework.data.jpa.repository.JpaRepository;

Expand All @@ -16,4 +18,19 @@ default User findByIdOrThrow(Integer userId) {
.orElseThrow(() -> new UnAuthorizedException(UNAUTHORIZED_USER.getErrorCode()));
}

List<User> findAllByIdIn(List<Integer> userIds);

default List<User> findAllByIdInOrThrow(List<Integer> userIds) {
List<User> users = findAllByIdIn(userIds);
List<Integer> foundUserIds = users.stream()
.map(User::getId)
.toList();

if (!foundUserIds.containsAll(userIds)) {
throw new NotFoundException(NOT_FOUND_USER.getErrorCode());
}

return users;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum ErrorStatus {
INVALID_INPUT_VALUE_FILTER("요청값 또는 토큰이 올바르지 않습니다."),
NOT_FOUND_MEETING("모임이 없습니다."),
NOT_FOUND_POST("존재하지 않는 게시글입니다."),
NOT_FOUND_USER("존재하지 않는 유저입니다."),
NOT_FOUND_COMMENT("존재하지 않는 댓글입니다."),
FULL_MEETING_CAPACITY("정원이 꽉 찼습니다."),
ALREADY_APPLIED_MEETING("이미 지원한 모임입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class MeetingV2CreateMeetingBodyDto {
+ " \"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"\n"
+ " ]", description = "모임 이미지 리스트, 최대 6개")
@NotEmpty
@Size(max = 6)
@Size(min = 1, max = 6)
mikekks marked this conversation as resolved.
Show resolved Hide resolved
private List<String> files;

@Schema(example = "스터디", description = "모임 카테고리")
Expand Down Expand Up @@ -79,8 +79,12 @@ public class MeetingV2CreateMeetingBodyDto {
+ " \"IOS\"\n"
+ " ]", description = "대상 파트 목록")
@NotNull
@Size(min = 1, max = 6)
private MeetingJoinablePart[] joinableParts;

@Schema(example = "\"[251, 942]\"", description = "공동 모임장 userId (크루에서 사용하는 userId)")
private List<Integer> coLeaderUserIds;

public String getmStartDate() {
return mStartDate;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.sopt.makers.crew.main.meeting.v2.dto.response;

import org.sopt.makers.crew.main.entity.user.User;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

@Schema(description = "공동 모임장 조회 dto")
public record MeetingV2CoLeaderResponseDto(
@Schema(description = "공동 모임장 id, 크루에서 사용하는 userId", example = "203")
@NotNull
Integer userId,
@Schema(description = "공동 모임장 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "789")
@NotNull
Integer orgId,
@Schema(description = "공동 모임장 이름", example = "공동 모임장 이름")
@NotNull
String userName,
@Schema(description = "공동 모임장 프로필 이미지 url", example = "[url 형식]")
@NotNull
String userprofileImage
) {
public static MeetingV2CoLeaderResponseDto of(User user) {
return new MeetingV2CoLeaderResponseDto(user.getId(), user.getOrgId(), user.getName(),
user.getProfileImage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.LocalDateTime;
import java.util.List;

import org.sopt.makers.crew.main.entity.meeting.CoLeader;
import org.sopt.makers.crew.main.global.dto.MeetingCreatorDto;
import org.sopt.makers.crew.main.entity.meeting.Meeting;
import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart;
Expand All @@ -11,6 +12,7 @@

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

Expand Down Expand Up @@ -59,11 +61,11 @@ public class MeetingV2GetMeetingByIdResponseDto {
@NotNull
private final String processDesc;

@Schema(description = "모임 활동 시작 시간", example = "2024-08-13T15:30:00", name = "mStartDate")
@Schema(description = "모임 활동 시작 시간", example = "2024-08-13T15:30:00", name = "mStartDate")
@NotNull
private final LocalDateTime mStartDate;

@Schema(description = "모임 활동 종료 시간", example = "2024-10-13T23:59:59", name = "mEndDate")
@Schema(description = "모임 활동 종료 시간", example = "2024-10-13T23:59:59", name = "mEndDate")
@NotNull
private final LocalDateTime mEndDate;

Expand Down Expand Up @@ -101,7 +103,16 @@ public class MeetingV2GetMeetingByIdResponseDto {
@NotNull
private final MeetingJoinablePart[] joinableParts;

@Schema(description = "모임 상태, 0: 모집전, 1: 모집중, 2: 모집종료", example = "1", type = "integer", allowableValues = {"0", "1", "2"})
@Schema(description = "공동 모임장 목록", example = "")
private final List<MeetingV2CoLeaderResponseDto> coMeetingLeaders;

@Schema(description = "공동 모임장 여부", example = "false")
@NotNull
@Getter(AccessLevel.NONE)
private final boolean isCoLeader;

@Schema(description = "모임 상태, 0: 모집전, 1: 모집중, 2: 모집종료", example = "1", type = "integer", allowableValues = {"0",
"1", "2"})
@NotNull
private final int status;

Expand Down Expand Up @@ -129,20 +140,26 @@ public class MeetingV2GetMeetingByIdResponseDto {
@NotNull
private final List<ApplyWholeInfoDto> appliedInfo;

public static MeetingV2GetMeetingByIdResponseDto of(Meeting meeting, long approvedCount, Boolean isHost, Boolean isApply,
public static MeetingV2GetMeetingByIdResponseDto of(Meeting meeting, List<CoLeader> coLeaders,
boolean isCoLeader, long approvedCount, Boolean isHost, Boolean isApply,
Boolean isApproved, User meetingCreator,
List<ApplyWholeInfoDto> appliedInfo, LocalDateTime now) {

MeetingCreatorDto meetingCreatorDto = MeetingCreatorDto.of(meetingCreator);

Integer meetingStatus = meeting.getMeetingStatus(now);

List<MeetingV2CoLeaderResponseDto> coLeaderResponseDtos = coLeaders.stream()
.map(coLeader -> MeetingV2CoLeaderResponseDto.of(coLeader.getUser()))
.toList();

return new MeetingV2GetMeetingByIdResponseDto(meeting.getId(), meeting.getUserId(), meeting.getTitle(),
meeting.getCategory().getValue(), meeting.getImageURL(), meeting.getStartDate(), meeting.getEndDate(),
meeting.getCapacity(), meeting.getDesc(), meeting.getProcessDesc(), meeting.getMStartDate(),
meeting.getMEndDate(), meeting.getLeaderDesc(), meeting.getNote(),
meeting.getIsMentorNeeded(), meeting.getCanJoinOnlyActiveGeneration(), meeting.getCreatedGeneration(),
meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meetingStatus,
meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), coLeaderResponseDtos, isCoLeader,
meetingStatus,
approvedCount, isHost, isApply, isApproved, meetingCreatorDto, appliedInfo);
}

Expand All @@ -153,4 +170,8 @@ public LocalDateTime getmStartDate() {
public LocalDateTime getmEndDate() {
return mEndDate;
}

public boolean getIsCoLeader() {
return this.isCoLeader;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

import lombok.RequiredArgsConstructor;

import org.sopt.makers.crew.main.entity.meeting.CoLeader;
import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository;
import org.sopt.makers.crew.main.entity.meeting.CoLeaders;
import org.sopt.makers.crew.main.global.dto.MeetingResponseDto;
import org.sopt.makers.crew.main.global.exception.BadRequestException;
import org.sopt.makers.crew.main.global.exception.ServerException;
Expand Down Expand Up @@ -91,6 +94,7 @@ public class MeetingV2ServiceImpl implements MeetingV2Service {
private final PostRepository postRepository;
private final CommentRepository commentRepository;
private final LikeRepository likeRepository;
private final CoLeaderRepository coLeaderRepository;

private final S3Service s3Service;

Expand Down Expand Up @@ -198,6 +202,14 @@ public MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBod
user.getId());

Meeting savedMeeting = meetingRepository.save(meeting);

List<Integer> coLeaderUserIds = requestBody.getCoLeaderUserIds();
if (coLeaderUserIds != null && !coLeaderUserIds.isEmpty()) {
List<User> users = userRepository.findAllByIdInOrThrow(coLeaderUserIds);
List<CoLeader> coLeaders = createCoLeaders(users, savedMeeting);
coLeaderRepository.saveAll(coLeaders);
}

return MeetingV2CreateMeetingResponseDto.of(savedMeeting.getId());
}

Expand Down Expand Up @@ -292,6 +304,7 @@ public void deleteMeeting(Integer meetingId, Integer userId) {
commentRepository.deleteAllByPostIdsInQuery(postIds);
postRepository.deleteAllByMeetingIdQuery(meetingId);
applyRepository.deleteAllByMeetingIdQuery(meetingId);
coLeaderRepository.deleteAllByMeetingId(meetingId);

meetingRepository.delete(meeting);
}
Expand All @@ -308,9 +321,23 @@ public void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto reque
createTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user,
user.getId());

updateCoLeaders(requestBody.getCoLeaderUserIds(), updatedMeeting);

meeting.updateMeeting(updatedMeeting);
}

private void updateCoLeaders(List<Integer> coLeaderUserIds, Meeting updatedMeeting) {
coLeaderRepository.deleteAllByMeetingId(updatedMeeting.getId());

if (coLeaderUserIds == null || coLeaderUserIds.isEmpty()) {
return;
}

List<User> users = userRepository.findAllById(coLeaderUserIds);
List<CoLeader> coLeaders = createCoLeaders(users, updatedMeeting);
coLeaderRepository.saveAll(coLeaders);
}

@Override
@Transactional
public void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId) {
Expand Down Expand Up @@ -353,14 +380,16 @@ public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Inte
User user = userRepository.findByIdOrThrow(userId);

Meeting meeting = meetingRepository.findByIdOrThrow(meetingId);
User meetingCreator = userRepository.findByIdOrThrow(meeting.getUserId());
User meetingLeader = userRepository.findByIdOrThrow(meeting.getUserId());
CoLeaders coLeaders = new CoLeaders(coLeaderRepository.findAllByMeetingId(meetingId));

Applies applies = new Applies(
applyRepository.findAllByMeetingIdWithUser(meetingId, List.of(WAITING, APPROVE, REJECT), ORDER_ASC));

Boolean isHost = meeting.checkMeetingLeader(user.getId());
Boolean isApply = applies.isApply(meetingId, user.getId());
Boolean isApproved = applies.isApproved(meetingId, user.getId());
boolean isCoLeader = coLeaders.isCoLeader(meetingId, userId);
long approvedCount = applies.getApprovedCount(meetingId);

List<ApplyWholeInfoDto> applyWholeInfoDtos = new ArrayList<>();
Expand All @@ -370,8 +399,9 @@ public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Inte
.toList();
}

return MeetingV2GetMeetingByIdResponseDto.of(meeting, approvedCount, isHost, isApply, isApproved,
meetingCreator, applyWholeInfoDtos, time.now());
return MeetingV2GetMeetingByIdResponseDto.of(meeting, coLeaders.getCoLeaders(meetingId), isCoLeader,
approvedCount, isHost, isApply, isApproved,
meetingLeader, applyWholeInfoDtos, time.now());
}

private void deleteCsvFile(String filePath) {
Expand Down Expand Up @@ -502,4 +532,13 @@ private void validateUserJoinableParts(User user, Meeting meeting) {
throw new BadRequestException(NOT_TARGET_PART.getErrorCode());
}
}

private List<CoLeader> createCoLeaders(List<User> coLeaders, Meeting savedMeeting) {
return coLeaders.stream()
.map(coLeader -> CoLeader.builder()
.meeting(savedMeeting)
.user(coLeader)
.build())
.toList();
}
}
Loading
Loading