-
Notifications
You must be signed in to change notification settings - Fork 2
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
Changes from 12 commits
115e5bc
2f0e6a0
1b45eab
51c1b79
02085ac
d35fb36
ac9dcc2
100f546
6435783
bcfb1f3
7540805
9e99418
7fe66ac
1197f41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 매퍼로 관리하면 좋을 것 같아요! |
||
.room(room) | ||
.memberId(memberId) | ||
|
@@ -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)); | ||
} | ||
|
||
|
@@ -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); | ||
} | ||
|
@@ -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; | ||
} | ||
|
@@ -171,4 +204,124 @@ private void decreaseRoomCount(Long memberId, RoomType roomType) { | |
|
||
member.exitNightRoom(); | ||
} | ||
|
||
private String getRoomManagerNickname(Long roomId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 결국에는 Response로 변환하는 메서드인 것 같아서 roomRoutines.stream().map(RoutineMapper::toRoutineResponse).toList(); There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. R: getParticipant를 지워야할 것 같아요! |
||
) | ||
.toList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. querydsl 코드에서 정렬하지 않은 이유가 있나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DB에서 정렬을 하면서 꺼내오는 방법도 있기는 한데 이렇게 되면 DB에 부담을 주고 성능에 더 영향을 줘서 DB에서는 관련 데이터만 꺼내오고 Spring에서 정렬하는거로 했습니다! DB는 데이터 꺼내고 저장하는 용도?로만 하는게 좋다고 영한쓰가 말했었습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 근데 최대 10명 * 4개라 메모리 비용이나 정렬 성능(미미하겠지만..) 측면에서 DB가 더 낫지 않을까 하는 생각도 드네욥! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Certifications- 복수로 사용하신 이유가 있나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
새로 추가한 DailyxxxCertification 엔티티들이랑 기존의 Certification은 하나의 SearchRepository로 관리하기 위해서 복수를 사용하고 구현했습니다!