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: 방 상세 정보 조회 기능 구현 #44

Merged
merged 14 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
13 changes: 13 additions & 0 deletions src/main/java/com/moabam/api/application/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import static com.moabam.global.error.model.ErrorMessage.*;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.moabam.api.domain.entity.Member;
import com.moabam.api.domain.repository.MemberRepository;
import com.moabam.api.domain.repository.MemberSearchRepository;
import com.moabam.global.error.exception.NotFoundException;

import lombok.RequiredArgsConstructor;
Expand All @@ -17,9 +20,19 @@
public class MemberService {

private final MemberRepository memberRepository;
private final MemberSearchRepository memberSearchRepository;

public Member getById(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND));
}

public Member getManager(Long roomId) {
return memberSearchRepository.findManager(roomId)
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND));
}

public List<Member> getRoomMembers(List<Long> memberIds) {
return memberRepository.findAllById(memberIds);
}
}
163 changes: 158 additions & 5 deletions src/main/java/com/moabam/api/application/RoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,41 @@
import static com.moabam.api.domain.entity.enums.RoomType.*;
import static com.moabam.global.error.model.ErrorMessage.*;

import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.moabam.api.domain.entity.Certification;
import com.moabam.api.domain.entity.DailyMemberCertification;
import com.moabam.api.domain.entity.DailyRoomCertification;
import com.moabam.api.domain.entity.Member;
import com.moabam.api.domain.entity.Participant;
import com.moabam.api.domain.entity.Room;
import com.moabam.api.domain.entity.Routine;
import com.moabam.api.domain.entity.enums.RoomType;
import com.moabam.api.domain.repository.CertificationsMapper;
import com.moabam.api.domain.repository.CertificationsSearchRepository;
import com.moabam.api.domain.repository.ParticipantRepository;
import com.moabam.api.domain.repository.ParticipantSearchRepository;
import com.moabam.api.domain.repository.RoomRepository;
import com.moabam.api.domain.repository.RoutineRepository;
import com.moabam.api.domain.repository.RoutineSearchRepository;
import com.moabam.api.dto.CertificationImageResponse;
import com.moabam.api.dto.CreateRoomRequest;
import com.moabam.api.dto.EnterRoomRequest;
import com.moabam.api.dto.ModifyRoomRequest;
import com.moabam.api.dto.RoomDetailsResponse;
import com.moabam.api.dto.RoomMapper;
import com.moabam.api.dto.RoutineMapper;
import com.moabam.api.dto.RoutineResponse;
import com.moabam.api.dto.TodayCertificateRankResponse;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.exception.ForbiddenException;
import com.moabam.global.error.exception.NotFoundException;
Expand All @@ -35,14 +51,16 @@ public class RoomService {

private final RoomRepository roomRepository;
private final RoutineRepository routineRepository;
private final RoutineSearchRepository routineSearchRepository;
private final ParticipantRepository participantRepository;
private final ParticipantSearchRepository participantSearchRepository;
private final CertificationsSearchRepository certificationsSearchRepository;
Copy link
Member

Choose a reason for hiding this comment

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

Certifications- 복수로 사용하신 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

새로 추가한 DailyxxxCertification 엔티티들이랑 기존의 Certification은 하나의 SearchRepository로 관리하기 위해서 복수를 사용하고 구현했습니다!

private final MemberService memberService;

@Transactional
public void createRoom(Long memberId, CreateRoomRequest createRoomRequest) {
Room room = RoomMapper.toRoomEntity(createRoomRequest);
List<Routine> routines = RoomMapper.toRoutineEntity(room, createRoomRequest.routines());
List<Routine> routines = RoutineMapper.toRoutineEntities(room, createRoomRequest.routines());
Participant participant = Participant.builder()
Copy link
Member

Choose a reason for hiding this comment

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

이 부분도 매퍼로 관리하면 좋을 것 같아요!

.room(room)
.memberId(memberId)
Expand Down Expand Up @@ -107,14 +125,32 @@ public void exitRoom(Long memberId, Long roomId) {
roomRepository.delete(room);
}

public RoomDetailsResponse getRoomDetails(Long memberId, Long roomId) {
LocalDate today = LocalDate.now();
Participant participant = getParticipant(memberId, roomId);
Room room = participant.getRoom();

String managerNickname = getRoomManagerNickname(roomId);
List<DailyMemberCertification> dailyMemberCertifications = getDailyMemberCertificationSorted(roomId, today);
List<RoutineResponse> routineResponses = getRoutineResponses(roomId);
List<TodayCertificateRankResponse> todayCertificateRankResponses = getTodayCertificateRankResponses(roomId,
routineResponses, dailyMemberCertifications, today);
List<LocalDate> certifiedDates = getCertifiedDates(roomId, today);
double completePercentage = calculateCompletePercentage(dailyMemberCertifications.size(),
room.getCurrentUserCount());

return RoomMapper.toRoomDetailsResponse(room, managerNickname, routineResponses, certifiedDates,
todayCertificateRankResponses, completePercentage);
}

public void validateRoomById(Long roomId) {
if (!roomRepository.existsById(roomId)) {
throw new NotFoundException(ROOM_NOT_FOUND);
}
}

private Participant getParticipant(Long memberId, Long roomId) {
return participantSearchRepository.findParticipant(memberId, roomId)
return participantSearchRepository.findByMemberIdAndRoomId(memberId, roomId)
.orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND));
}

Expand All @@ -126,11 +162,9 @@ private void validateRoomEnter(Long memberId, String requestPassword, Room room)
if (!isEnterRoomAvailable(memberId, room.getRoomType())) {
throw new BadRequestException(MEMBER_ROOM_EXCEED);
}

if (!StringUtils.isEmpty(requestPassword) && !room.getPassword().equals(requestPassword)) {
throw new BadRequestException(WRONG_ROOM_PASSWORD);
}

if (room.getCurrentUserCount() == room.getMaxUserCount()) {
throw new BadRequestException(ROOM_MAX_USER_REACHED);
}
Expand All @@ -142,7 +176,6 @@ private boolean isEnterRoomAvailable(Long memberId, RoomType roomType) {
if (roomType.equals(MORNING) && member.getCurrentMorningCount() >= 3) {
return false;
}

if (roomType.equals(NIGHT) && member.getCurrentNightCount() >= 3) {
return false;
}
Expand Down Expand Up @@ -171,4 +204,124 @@ private void decreaseRoomCount(Long memberId, RoomType roomType) {

member.exitNightRoom();
}

private String getRoomManagerNickname(Long roomId) {
Copy link
Member

Choose a reason for hiding this comment

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

C: 해당 기능은 메서드로 분리하지 않아도 될 것 같습니다!

return memberService.getManager(roomId).getNickname();
}

private List<RoutineResponse> getRoutineResponses(Long roomId) {
List<Routine> roomRoutines = routineSearchRepository.findByRoomId(roomId);

return roomRoutines.stream().map(RoutineMapper::toRoutineResponse).toList();
}
Copy link
Member

Choose a reason for hiding this comment

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

결국에는 Response로 변환하는 메서드인 것 같아서 roomRoutines.stream().map(RoutineMapper::toRoutineResponse).toList();
이 로직 자체를 Mapper에서 하는 건 어떤가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

오호 그러네요 저 부분은 Mapper로 올리는게 좋을 것 같습니다! 👍


private List<TodayCertificateRankResponse> getTodayCertificateRankResponses(Long roomId,
List<RoutineResponse> routines, List<DailyMemberCertification> dailyMemberCertifications, LocalDate today) {

List<TodayCertificateRankResponse> responses = new ArrayList<>();
List<Long> routineIds = routines.stream()
.map(RoutineResponse::routineId)
.toList();
List<Certification> certifications = certificationsSearchRepository.findCertifications(
routineIds,
today);
List<Participant> participants = participantSearchRepository.findParticipants(roomId);
List<Member> members = memberService.getRoomMembers(participants.stream()
.map(Participant::getMemberId)
.toList());

addCompletedMembers(responses, dailyMemberCertifications, members, certifications, participants, today);
addUncompletedMembers(responses, dailyMemberCertifications, members, participants, today);

return responses;
}

private List<DailyMemberCertification> getDailyMemberCertificationSorted(Long roomId, LocalDate date) {
List<DailyMemberCertification> dailyMemberCertifications =
certificationsSearchRepository.findDailyMemberCertifications(roomId, date);

return dailyMemberCertifications.stream()
.sorted(Comparator.comparing(
dailyMemberCertification -> dailyMemberCertification.getParticipant().getCreatedAt())
Copy link
Member

Choose a reason for hiding this comment

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

R: getParticipant를 지워야할 것 같아요!

)
.toList();
Copy link
Member

Choose a reason for hiding this comment

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

querydsl 코드에서 정렬하지 않은 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

DB에서 정렬을 하면서 꺼내오는 방법도 있기는 한데 이렇게 되면 DB에 부담을 주고 성능에 더 영향을 줘서 DB에서는 관련 데이터만 꺼내오고 Spring에서 정렬하는거로 했습니다! DB는 데이터 꺼내고 저장하는 용도?로만 하는게 좋다고 영한쓰가 말했었습니다.
이게 데이터가 수만개로 늘어난다는 가정을 하면 더 이해가 될 것 같습니다!

Copy link
Member

@kmebin kmebin Nov 6, 2023

Choose a reason for hiding this comment

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

오호 근데 최대 10명 * 4개라 메모리 비용이나 정렬 성능(미미하겠지만..) 측면에서 DB가 더 낫지 않을까 하는 생각도 드네욥!

Copy link
Member Author

@ymkim97 ymkim97 Nov 6, 2023

Choose a reason for hiding this comment

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

헐 그렇긴하네요 ㅋ.ㅋ 이거까진 생각 못했습니다 감삼다👍

}

private void addCompletedMembers(List<TodayCertificateRankResponse> responses,
List<DailyMemberCertification> dailyMemberCertifications, List<Member> members,
List<Certification> certifications, List<Participant> participants, LocalDate today) {

int rank = 1;

for (DailyMemberCertification certification : dailyMemberCertifications) {
Member member = members.stream()
.filter(m -> m.getId().equals(certification.getMemberId()))
.findAny()
.orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR));

int contributionPoint = calculateContributionPoint(member.getId(), participants, today);
List<CertificationImageResponse> cftImageResponses = CertificationsMapper.toCftImageResponses(
member.getId(),
certifications);

TodayCertificateRankResponse response = CertificationsMapper.toTodayCftRankResponse(
rank, member, contributionPoint, "https://~awake", "https://~sleep", cftImageResponses);

rank += 1;
responses.add(response);
}
}

private void addUncompletedMembers(List<TodayCertificateRankResponse> responses,
List<DailyMemberCertification> dailyMemberCertifications, List<Member> members,
List<Participant> participants, LocalDate today) {

List<Long> allMemberIds = participants.stream()
.map(Participant::getMemberId)
.collect(Collectors.toList());

List<Long> certifiedMemberIds = dailyMemberCertifications.stream()
.map(DailyMemberCertification::getMemberId)
.toList();

allMemberIds.removeAll(certifiedMemberIds);

for (Long memberId : allMemberIds) {
Member member = members.stream()
.filter(m -> m.getId().equals(memberId))
.findAny()
.orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR));

int contributionPoint = calculateContributionPoint(memberId, participants, today);

TodayCertificateRankResponse response = CertificationsMapper.toTodayCftRankResponse(
500, member, contributionPoint, "https://~awake", "https://~sleep", null);

responses.add(response);
}
}

private int calculateContributionPoint(Long memberId, List<Participant> participants, LocalDate today) {
Participant participant = participants.stream()
.filter(p -> p.getMemberId().equals(memberId))
.findAny()
.orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR));

int participatedDays = Period.between(participant.getCreatedAt().toLocalDate(), today).getDays() + 1;

return (int)(((double)participant.getCertifyCount() / participatedDays) * 100);
}

private List<LocalDate> getCertifiedDates(Long roomId, LocalDate today) {
List<DailyRoomCertification> certifications = certificationsSearchRepository.findDailyRoomCertifications(
roomId, today);

return certifications.stream().map(DailyRoomCertification::getCertifiedAt).toList();
}

private double calculateCompletePercentage(int certifiedMembersCount, int currentsMembersCount) {
double completePercentage = ((double)certifiedMembersCount / currentsMembersCount) * 100;

return Math.round(completePercentage * 100) / 100.0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.moabam.api.domain.entity;

import static java.util.Objects.*;

import com.moabam.global.common.entity.BaseTimeEntity;

import jakarta.persistence.Column;
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 lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "daily_member_certification") // 매일 사용자가 방에 인증을 완료했는지 -> createdAt으로 인증 시각 확인
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class DailyMemberCertification extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "member_id", nullable = false, updatable = false)
private Long memberId;

@Column(name = "room_id", nullable = false, updatable = false)
private Long roomId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "participant_id") // Participant createdAt으로 방 참여 시작 날짜 확인, certifyCount 가져다가 쓰기
private Participant participant;

@Builder
private DailyMemberCertification(Long id, Long memberId, Long roomId, Participant participant) {
this.id = id;
this.memberId = requireNonNull(memberId);
this.roomId = requireNonNull(roomId);
this.participant = requireNonNull(participant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.moabam.api.domain.entity;

import static java.util.Objects.*;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "daily_room_certification") // 매일 방이 인증을 완료했는지 -> certifiedAt으로 인증 날짜 확인
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class DailyRoomCertification {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "room_id", nullable = false, updatable = false)
private Long roomId;

@Column(name = "certified_at", nullable = false, updatable = false)
private LocalDate certifiedAt;

@Builder
private DailyRoomCertification(Long id, Long roomId, LocalDate certifiedAt) {
this.id = id;
this.roomId = requireNonNull(roomId);
this.certifiedAt = requireNonNull(certifiedAt);
}
}
6 changes: 4 additions & 2 deletions src/main/java/com/moabam/api/domain/entity/Participant.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import org.hibernate.annotations.SQLDelete;

import com.moabam.global.common.entity.BaseTimeEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand All @@ -25,15 +27,15 @@
@Table(name = "participant")
@SQLDelete(sql = "UPDATE participant SET deleted_at = CURRENT_TIMESTAMP where id = ?")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Participant {
public class Participant extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id", updatable = false)
@JoinColumn(name = "room_id")
private Room room;

@Column(name = "member_id", updatable = false, nullable = false)
Expand Down
Loading