diff --git a/layer-api/src/main/java/org/layer/LayerApplication.java b/layer-api/src/main/java/org/layer/LayerApplication.java index 15e24e21..d82fd23e 100644 --- a/layer-api/src/main/java/org/layer/LayerApplication.java +++ b/layer-api/src/main/java/org/layer/LayerApplication.java @@ -11,8 +11,10 @@ import org.springframework.scheduling.annotation.EnableAsync; @OpenAPIDefinition(servers = { + @Server(url = "http://localhost:8080", description = "로컬서버"), @Server(url = "https://stgapi.layerapp.io", description = "개발서버"), - @Server(url = "https://api.layerapp.io", description = "운영서버")}) + @Server(url = "https://api.layerapp.io", description = "운영서버") +}) @SpringBootApplication @EnableJpaAuditing @EnableAspectJAutoProxy diff --git a/layer-api/src/main/java/org/layer/domain/actionItem/service/ActionItemService.java b/layer-api/src/main/java/org/layer/domain/actionItem/service/ActionItemService.java index 85844f1a..8e8821cc 100644 --- a/layer-api/src/main/java/org/layer/domain/actionItem/service/ActionItemService.java +++ b/layer-api/src/main/java/org/layer/domain/actionItem/service/ActionItemService.java @@ -40,9 +40,6 @@ public class ActionItemService { @Transactional public ActionItemCreateResponse createActionItem(Long memberId, Long retrospectId, String content) { - log.info("?"); - log.info("?"); - // 만드는 사람이 스페이스 리더인지 확인 Retrospect retrospect = retrospectRepository.findByIdOrThrow(retrospectId); Space space = spaceRepository.findByIdOrThrow(retrospect.getSpaceId()); diff --git a/layer-api/src/main/java/org/layer/domain/analyze/service/AnalyzeService.java b/layer-api/src/main/java/org/layer/domain/analyze/service/AnalyzeService.java index 8dd17900..8e552186 100644 --- a/layer-api/src/main/java/org/layer/domain/analyze/service/AnalyzeService.java +++ b/layer-api/src/main/java/org/layer/domain/analyze/service/AnalyzeService.java @@ -1,10 +1,8 @@ package org.layer.domain.analyze.service; -import static org.layer.domain.answer.entity.Answers.*; - -import java.util.ArrayList; -import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.layer.domain.analyze.controller.dto.response.AnalyzeIndividualGetResponse; import org.layer.domain.analyze.controller.dto.response.AnalyzeTeamGetResponse; import org.layer.domain.analyze.controller.dto.response.AnalyzesGetResponse; @@ -21,8 +19,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import java.util.List; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -30,36 +28,35 @@ @Slf4j public class AnalyzeService { - private final AnalyzeRepository analyzeRepository; - private final MemberSpaceRelationRepository memberSpaceRelationRepository; - private final SpaceRepository spaceRepository; - - private final AIAnalyzeService aiAnalyzeService; + private final AnalyzeRepository analyzeRepository; + private final MemberSpaceRelationRepository memberSpaceRelationRepository; + private final SpaceRepository spaceRepository; - @Transactional - @Async - public void createAnalyzeTemp(Long spaceId, Long retrospectId, List memberIds) { - aiAnalyzeService.createAnalyze(spaceId, retrospectId, memberIds); - } + private final AIAnalyzeService aiAnalyzeService; - public AnalyzesGetResponse getAnalyze(Long spaceId, Long retrospectId, Long memberId) { - // 해당 스페이스 팀원인지 검증 - Team team = new Team(memberSpaceRelationRepository.findAllBySpaceId(spaceId)); - team.validateTeamMembership(memberId); + @Transactional + @Async + public void createAnalyzeTemp(Long spaceId, Long retrospectId, List memberIds) { + aiAnalyzeService.createAnalyze(spaceId, retrospectId, memberIds); + } - Space space = spaceRepository.findByIdOrThrow(spaceId); + public AnalyzesGetResponse getAnalyze(Long spaceId, Long retrospectId, Long memberId) { + // 해당 스페이스 팀원인지 검증 + Team team = new Team(memberSpaceRelationRepository.findAllBySpaceId(spaceId)); + team.validateTeamMembership(memberId); - AnalyzeTeamGetResponse analyzeTeamGetResponse = null; - if(space.getCategory().equals(SpaceCategory.TEAM)) { - Analyze teamAnalyze = analyzeRepository.findByRetrospectIdAndAnalyzeTypeOrThrow(retrospectId, - AnalyzeType.TEAM); - analyzeTeamGetResponse = AnalyzeTeamGetResponse.of(teamAnalyze); - } + Space space = spaceRepository.findByIdOrThrow(spaceId); - Analyze individualAnalyze = analyzeRepository.findByRetrospectIdAndAnalyzeTypeAndMemberIdOrThrow(retrospectId, - AnalyzeType.INDIVIDUAL, memberId); + AnalyzeTeamGetResponse analyzeTeamGetResponse = null; + if (space.getCategory().equals(SpaceCategory.TEAM)) { + Analyze teamAnalyze = analyzeRepository.findByRetrospectIdAndAnalyzeTypeOrThrow(retrospectId, + AnalyzeType.TEAM); + analyzeTeamGetResponse = AnalyzeTeamGetResponse.of(teamAnalyze); + } - return AnalyzesGetResponse.of(analyzeTeamGetResponse, AnalyzeIndividualGetResponse.of(individualAnalyze)); - } + Optional individualAnalyze = analyzeRepository.findByRetrospectIdAndAnalyzeTypeAndMemberId(retrospectId, + AnalyzeType.INDIVIDUAL, memberId); + return AnalyzesGetResponse.of(analyzeTeamGetResponse, individualAnalyze.map(AnalyzeIndividualGetResponse::of).orElse(null)); + } } diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/MemberApi.java b/layer-api/src/main/java/org/layer/domain/member/controller/MemberApi.java index 634bff28..4e60455d 100644 --- a/layer-api/src/main/java/org/layer/domain/member/controller/MemberApi.java +++ b/layer-api/src/main/java/org/layer/domain/member/controller/MemberApi.java @@ -5,6 +5,8 @@ import jakarta.validation.Valid; import org.layer.common.annotation.MemberId; import org.layer.domain.member.controller.dto.CreateFeedbackRequest; +import org.layer.domain.member.controller.dto.GetMemberAnalyzesResponse; + import org.layer.domain.member.controller.dto.UpdateMemberInfoRequest; import org.layer.domain.member.controller.dto.UpdateMemberInfoResponse; import org.springframework.http.ResponseEntity; @@ -17,4 +19,8 @@ public interface MemberApi { @Operation(summary = "서비스 사용에 대한 피드백 남기기", method = "POST") ResponseEntity createFeedback(@MemberId Long memberId, @Valid CreateFeedbackRequest createFeedbackRequest); + + @Operation(summary = "내 회고 분석 조회", method = "GET") + ResponseEntity getMyAnalyzes(@MemberId Long memberId); + } diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/MemberController.java b/layer-api/src/main/java/org/layer/domain/member/controller/MemberController.java index 9694e3bb..c4cfbdc7 100644 --- a/layer-api/src/main/java/org/layer/domain/member/controller/MemberController.java +++ b/layer-api/src/main/java/org/layer/domain/member/controller/MemberController.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.layer.common.annotation.MemberId; import org.layer.domain.member.controller.dto.CreateFeedbackRequest; +import org.layer.domain.member.controller.dto.GetMemberAnalyzesResponse; import org.layer.domain.member.controller.dto.UpdateMemberInfoRequest; import org.layer.domain.member.controller.dto.UpdateMemberInfoResponse; import org.layer.domain.member.service.MemberService; @@ -31,4 +32,10 @@ public ResponseEntity createFeedback(@MemberId Long memberId, @Valid @Requ memberService.createFeedback(memberId, createFeedbackRequest); return null; } + @Override + @GetMapping("/analyze") + public ResponseEntity getMyAnalyzes(@MemberId Long memberId) { + + return ResponseEntity.ok(memberService.getMyAnalyzes(memberId)); + } } diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberAnalyzesResponse.java b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberAnalyzesResponse.java new file mode 100644 index 00000000..da0af54e --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberAnalyzesResponse.java @@ -0,0 +1,20 @@ +package org.layer.domain.member.controller.dto; + +import java.util.List; + +public record GetMemberAnalyzesResponse( + List recentAnalyzes, + List goodAnalyzes, + List badAnalyzes, + List improvementAnalyzes + +) { + + public static GetMemberAnalyzesResponse of(List recentAnalyzes, + List goodAnalyzes, + List badAnalyzes, + List improvementAnalyzes) { + + return new GetMemberAnalyzesResponse(recentAnalyzes, goodAnalyzes, badAnalyzes, improvementAnalyzes); + } +} diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentAnalyzeResponse.java b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentAnalyzeResponse.java new file mode 100644 index 00000000..b0ae2b4e --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentAnalyzeResponse.java @@ -0,0 +1,20 @@ +package org.layer.domain.member.controller.dto; + +import java.time.LocalDateTime; + +import org.layer.domain.retrospect.dto.SpaceRetrospectDto; + +public record GetMemberRecentAnalyzeResponse( + Long spaceId, + String spaceName, + Long retrospectId, + String retrospectTitle, + LocalDateTime deadline +) { + public static GetMemberRecentAnalyzeResponse of(SpaceRetrospectDto spaceRetrospectDto) { + return new GetMemberRecentAnalyzeResponse(spaceRetrospectDto.getSpace().getId(), + spaceRetrospectDto.getSpace().getName(), spaceRetrospectDto.getRetrospect().getId(), + spaceRetrospectDto.getRetrospect().getTitle(), spaceRetrospectDto.getRetrospect().getDeadline()); + } + +} diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentBadAnalyzeResponse.java b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentBadAnalyzeResponse.java new file mode 100644 index 00000000..725d65bf --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentBadAnalyzeResponse.java @@ -0,0 +1,20 @@ +package org.layer.domain.member.controller.dto; + +import java.time.LocalDateTime; + +import org.layer.domain.retrospect.dto.SpaceRetrospectDto; + +public record GetMemberRecentBadAnalyzeResponse( + Long spaceId, + String spaceName, + Long retrospectId, + String retrospectTitle, + LocalDateTime deadline, + String badPoint +) { + public static GetMemberRecentBadAnalyzeResponse of(SpaceRetrospectDto spaceRetrospectDto, String badPoint) { + return new GetMemberRecentBadAnalyzeResponse(spaceRetrospectDto.getSpace().getId(), + spaceRetrospectDto.getSpace().getName(), spaceRetrospectDto.getRetrospect().getId(), + spaceRetrospectDto.getRetrospect().getTitle(), spaceRetrospectDto.getRetrospect().getDeadline(), badPoint); + } +} diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentGoodAnalyzeResponse.java b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentGoodAnalyzeResponse.java new file mode 100644 index 00000000..f71cda96 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentGoodAnalyzeResponse.java @@ -0,0 +1,21 @@ +package org.layer.domain.member.controller.dto; + +import java.time.LocalDateTime; + +import org.layer.domain.retrospect.dto.SpaceRetrospectDto; + +public record GetMemberRecentGoodAnalyzeResponse( + Long spaceId, + String spaceName, + Long retrospectId, + String retrospectTitle, + LocalDateTime deadline, + String goodPoint +) { + public static GetMemberRecentGoodAnalyzeResponse of(SpaceRetrospectDto spaceRetrospectDto, String goodPoint) { + return new GetMemberRecentGoodAnalyzeResponse(spaceRetrospectDto.getSpace().getId(), + spaceRetrospectDto.getSpace().getName(), spaceRetrospectDto.getRetrospect().getId(), + spaceRetrospectDto.getRetrospect().getTitle(), spaceRetrospectDto.getRetrospect().getDeadline(), goodPoint); + } + +} diff --git a/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentImprovementAnalyzeResponse.java b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentImprovementAnalyzeResponse.java new file mode 100644 index 00000000..d08355c0 --- /dev/null +++ b/layer-api/src/main/java/org/layer/domain/member/controller/dto/GetMemberRecentImprovementAnalyzeResponse.java @@ -0,0 +1,20 @@ +package org.layer.domain.member.controller.dto; + +import java.time.LocalDateTime; + +import org.layer.domain.retrospect.dto.SpaceRetrospectDto; + +public record GetMemberRecentImprovementAnalyzeResponse( + Long spaceId, + String spaceName, + Long retrospectId, + String retrospectTitle, + LocalDateTime deadline, + String improvementPoint +) { + public static GetMemberRecentImprovementAnalyzeResponse of(SpaceRetrospectDto spaceRetrospectDto, String improvementPoint) { + return new GetMemberRecentImprovementAnalyzeResponse(spaceRetrospectDto.getSpace().getId(), + spaceRetrospectDto.getSpace().getName(), spaceRetrospectDto.getRetrospect().getId(), + spaceRetrospectDto.getRetrospect().getTitle(), spaceRetrospectDto.getRetrospect().getDeadline(), improvementPoint); + } +} diff --git a/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java index 9bf1ccc4..7369f0b9 100644 --- a/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java +++ b/layer-api/src/main/java/org/layer/domain/member/service/MemberService.java @@ -3,22 +3,43 @@ import lombok.RequiredArgsConstructor; import org.layer.common.exception.BaseCustomException; import org.layer.common.exception.MemberExceptionType; +import org.layer.domain.analyze.entity.Analyze; +import org.layer.domain.analyze.entity.AnalyzeDetail; +import org.layer.domain.analyze.enums.AnalyzeDetailType; +import org.layer.domain.analyze.repository.AnalyzeRepository; import org.layer.domain.auth.controller.dto.SignUpRequest; +import org.layer.domain.common.time.Time; import org.layer.domain.external.google.enums.SheetType; import org.layer.domain.external.google.service.GoogleApiService; import org.layer.domain.jwt.SecurityUtil; import org.layer.domain.member.controller.dto.CreateFeedbackRequest; +import org.layer.domain.member.controller.dto.GetMemberAnalyzesResponse; +import org.layer.domain.member.controller.dto.GetMemberRecentAnalyzeResponse; +import org.layer.domain.member.controller.dto.GetMemberRecentBadAnalyzeResponse; +import org.layer.domain.member.controller.dto.GetMemberRecentGoodAnalyzeResponse; +import org.layer.domain.member.controller.dto.GetMemberRecentImprovementAnalyzeResponse; import org.layer.domain.member.controller.dto.UpdateMemberInfoRequest; import org.layer.domain.member.controller.dto.UpdateMemberInfoResponse; import org.layer.domain.member.entity.Member; import org.layer.domain.member.entity.MemberFeedback; import org.layer.domain.member.entity.SocialType; import org.layer.domain.member.repository.MemberRepository; +import org.layer.domain.retrospect.dto.SpaceRetrospectDto; +import org.layer.domain.retrospect.entity.RetrospectStatus; +import org.layer.domain.retrospect.repository.RetrospectRepository; +import org.layer.domain.space.entity.MemberSpaceRelation; +import org.layer.domain.space.entity.Space; +import org.layer.domain.space.repository.MemberSpaceRelationRepository; import org.layer.oauth.dto.service.MemberInfoServiceResponse; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import static org.layer.common.exception.MemberExceptionType.NOT_A_NEW_MEMBER; import static org.layer.common.exception.MemberExceptionType.NOT_FOUND_USER; @@ -27,97 +48,140 @@ @RequiredArgsConstructor @Service public class MemberService { - private final SecurityUtil securityUtil; - private final MemberRepository memberRepository; - - private final GoogleApiService googleApiService; - - public Member findMemberById(Long memberId) { - return memberRepository.findById(memberId).orElse(null); - } - - // 소셜 아이디와 소셜 타입으로 멤버 찾기. 멤버가 없으면 Exception - // 현재는 사용하지 않음 - public Member getMemberBySocialIdAndSocialType(String socialId, SocialType socialType) { - return memberRepository.findBySocialIdAndSocialType(socialId, socialType) - .orElseThrow(() -> new BaseCustomException(MemberExceptionType.NOT_FOUND_USER)); - } - - // sign-in만을 위한 메서드. 멤버가 없을시 회원가입이 필요함을 알려준다. - // 회원이 진짜로 없는 error의 경우와 회원 가입이 필요하다는 응답을 구분하기 위함 - public Member getMemberBySocialInfoForSignIn(String socialId, SocialType socialType) { - return memberRepository.findBySocialIdAndSocialType(socialId, socialType) - .orElseThrow(() -> new BaseCustomException(MemberExceptionType.NEED_TO_REGISTER)); - } - - public void checkIsNewMember(String socialId, SocialType socialType) { - Optional memberOpt = memberRepository.findBySocialIdAndSocialType(socialId, socialType); - - if (memberOpt.isPresent()) { - throw new BaseCustomException(NOT_A_NEW_MEMBER); - } - } - - - public void createFeedback(Long memberId, CreateFeedbackRequest createFeedbackRequest) { - var foundMemberFeedback = findFeedback(memberId); - if (foundMemberFeedback.isEmpty()) { - return; - } - googleApiService.writeFeedback(SheetType.FEEDBACK, foundMemberFeedback.get(), createFeedbackRequest.satisfaction(), createFeedbackRequest.description()); - } - - - @Transactional - public Member saveMember(SignUpRequest signUpRequest, MemberInfoServiceResponse memberInfo) { - Member member = Member.builder() - .name(signUpRequest.name()) - .memberRole(USER) - .email(memberInfo.email()) - .socialId(memberInfo.socialId()) - .socialType(memberInfo.socialType()) - .build(); - - memberRepository.save(member); - - return member; - } - - public Member getCurrentMember() { - return memberRepository - .findById(securityUtil.getCurrentMemberId()) - .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); - } - - public Member getMemberByMemberId(Long memberId) { - return memberRepository. - findById(memberId) - .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); - } - - @Transactional - public void withdrawMember(Long memberId) { - Member currentMember = getCurrentMember(); - memberRepository.delete(currentMember); - } - - - @Transactional - public UpdateMemberInfoResponse updateMemberInfo(Long memberId, UpdateMemberInfoRequest updateMemberInfoRequest) { - Member member = memberRepository.findByIdOrThrow(memberId); - member.updateName(updateMemberInfoRequest.name()); - member.updateProfileImageUrl(updateMemberInfoRequest.profileImageUrl()); - - - return UpdateMemberInfoResponse.builder() - .memberId(member.getId()) - .name(member.getName()) - .profileImageUrl(member.getProfileImageUrl()) - .build(); - } - - public Optional findFeedback(Long memberId) { - return memberRepository.findAllMemberFeedback(memberId); - } - +private static final int TWO_MONTHS = 2; + + private final MemberRepository memberRepository; + private final MemberSpaceRelationRepository memberSpaceRelationRepository; + private final RetrospectRepository retrospectRepository; + private final AnalyzeRepository analyzeRepository; + + private final GoogleApiService googleApiService; + + private final SecurityUtil securityUtil; + + private final Time time; + + // 소셜 아이디와 소셜 타입으로 멤버 찾기. 멤버가 없으면 Exception + // 현재는 사용하지 않음 + public Member getMemberBySocialIdAndSocialType(String socialId, SocialType socialType) { + return memberRepository.findBySocialIdAndSocialType(socialId, socialType) + .orElseThrow(() -> new BaseCustomException(MemberExceptionType.NOT_FOUND_USER)); + } + + // sign-in만을 위한 메서드. 멤버가 없을시 회원가입이 필요함을 알려준다. + // 회원이 진짜로 없는 error의 경우와 회원 가입이 필요하다는 응답을 구분하기 위함 + public Member getMemberBySocialInfoForSignIn(String socialId, SocialType socialType) { + return memberRepository.findBySocialIdAndSocialType(socialId, socialType) + .orElseThrow(() -> new BaseCustomException(MemberExceptionType.NEED_TO_REGISTER)); + } + + public void checkIsNewMember(String socialId, SocialType socialType) { + Optional memberOpt = memberRepository.findBySocialIdAndSocialType(socialId, socialType); + + if (memberOpt.isPresent()) { + throw new BaseCustomException(NOT_A_NEW_MEMBER); + } + } + + public void createFeedback(Long memberId, CreateFeedbackRequest createFeedbackRequest) { + var foundMemberFeedback = findFeedback(memberId); + if (foundMemberFeedback.isEmpty()) { + return; + } + googleApiService.writeFeedback(SheetType.FEEDBACK, foundMemberFeedback.get(), + createFeedbackRequest.satisfaction(), createFeedbackRequest.description()); + } + + @Transactional + public Member saveMember(SignUpRequest signUpRequest, MemberInfoServiceResponse memberInfo) { + Member member = Member.builder() + .name(signUpRequest.name()) + .memberRole(USER) + .email(memberInfo.email()) + .socialId(memberInfo.socialId()) + .socialType(memberInfo.socialType()) + .build(); + + memberRepository.save(member); + + return member; + } + + public Member getCurrentMember() { + return memberRepository + .findById(securityUtil.getCurrentMemberId()) + .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); + } + + public Member getMemberByMemberId(Long memberId) { + return memberRepository. + findById(memberId) + .orElseThrow(() -> new BaseCustomException(NOT_FOUND_USER)); + } + + @Transactional + public void withdrawMember(Long memberId) { + Member currentMember = getCurrentMember(); + memberRepository.delete(currentMember); + } + + @Transactional + public UpdateMemberInfoResponse updateMemberInfo(Long memberId, UpdateMemberInfoRequest updateMemberInfoRequest) { + Member member = memberRepository.findByIdOrThrow(memberId); + member.updateName(updateMemberInfoRequest.name()); + member.updateProfileImageUrl(updateMemberInfoRequest.profileImageUrl()); + + return UpdateMemberInfoResponse.builder() + .memberId(member.getId()) + .name(member.getName()) + .profileImageUrl(member.getProfileImageUrl()) + .build(); + } + + public Optional findFeedback(Long memberId) { + return memberRepository.findAllMemberFeedback(memberId); + } + + @Transactional(readOnly = true) + public GetMemberAnalyzesResponse getMyAnalyzes(Long memberId) { + List memberSpaceRelations = memberSpaceRelationRepository.findAllByMemberId(memberId); + List spaceIds = memberSpaceRelations.stream().map(m -> m.getSpace().getId()).toList(); + + List recentRetrospects = new ArrayList<>(); + spaceIds.forEach(spaceId -> { + Optional spaceRetrospectDto = retrospectRepository.findFirstBySpaceIdAndRetrospectStatusAndDeadlineAfterOrderByDeadline( + spaceId, RetrospectStatus.DONE, time.now().minusMonths(TWO_MONTHS)); + spaceRetrospectDto.ifPresent(recentRetrospects::add); + }); + + List retrospectIds = recentRetrospects.stream().map(r -> r.getRetrospect().getId()).toList(); + Map spaceRetrospectDtoMap = recentRetrospects.stream() + .collect(Collectors.toMap(r -> r.getRetrospect().getId(), r -> r)); + + List analyzes = analyzeRepository.findAllByMemberIdAndRetrospectIdInQuery(memberId, retrospectIds); + + return getMemberAnalyzeResponseDto(spaceRetrospectDtoMap, analyzes); + } + + private GetMemberAnalyzesResponse getMemberAnalyzeResponseDto(Map spaceRetrospectDtoMap, List analyzes) { + List recentAnalyzes = new ArrayList<>(); + List goodAnalyzes = new ArrayList<>(); + List badAnalyzes = new ArrayList<>(); + List improvementAnalyzes = new ArrayList<>(); + + analyzes.forEach(analyze -> { + AnalyzeDetail goodAnalyzeDetail = analyze.getTopCountAnalyzeDetailBy(AnalyzeDetailType.GOOD); + AnalyzeDetail badAnalyzeDetail = analyze.getTopCountAnalyzeDetailBy(AnalyzeDetailType.BAD); + AnalyzeDetail improvementAnalyzeDetail = analyze.getTopCountAnalyzeDetailBy(AnalyzeDetailType.IMPROVEMENT); + SpaceRetrospectDto spaceRetrospectDto = spaceRetrospectDtoMap.get(analyze.getRetrospectId()); + + recentAnalyzes.add(GetMemberRecentAnalyzeResponse.of(spaceRetrospectDto)); + goodAnalyzes.add(GetMemberRecentGoodAnalyzeResponse.of(spaceRetrospectDto, goodAnalyzeDetail.getContent())); + badAnalyzes.add(GetMemberRecentBadAnalyzeResponse.of(spaceRetrospectDto, badAnalyzeDetail.getContent())); + improvementAnalyzes.add(GetMemberRecentImprovementAnalyzeResponse.of(spaceRetrospectDto, + improvementAnalyzeDetail.getContent())); + }); + + return GetMemberAnalyzesResponse.of(recentAnalyzes, goodAnalyzes, badAnalyzes, improvementAnalyzes); + } } diff --git a/layer-api/src/main/java/org/layer/domain/template/controller/dto/TemplateDetailInfoResponse.java b/layer-api/src/main/java/org/layer/domain/template/controller/dto/TemplateDetailInfoResponse.java index e7301455..1ab55624 100644 --- a/layer-api/src/main/java/org/layer/domain/template/controller/dto/TemplateDetailInfoResponse.java +++ b/layer-api/src/main/java/org/layer/domain/template/controller/dto/TemplateDetailInfoResponse.java @@ -6,7 +6,10 @@ import org.layer.domain.form.entity.Form; import org.layer.domain.template.entity.TemplateMetadata; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; + @Builder @Schema @@ -21,7 +24,7 @@ public record TemplateDetailInfoResponse( @NotNull String templateName, // ex. KPT 회고 @Schema(description = "템플릿 대표 사진", example = "[이미지 url]") - @NotNull + // @NotNull String templateImageUrl, @Schema(description = "회고에 대한 설명", example = "회고 내용을 Keep, Problem, Try 세가지 관점으로 분류하여... [생략]") @NotNull @@ -38,20 +41,25 @@ public record TemplateDetailInfoResponse( List templateDetailQuestionList, // 질문(회고 과정)에 대한 설명 @Schema(description = "템플릿 목적 태그 리스트") - List templatePurposeResponseList // 질문(회고 과정)에 대한 설명 + List templatePurposeResponseList, // 질문(회고 과정)에 대한 설명 + + + @Schema(description = "템플릿 생성 일자") + LocalDateTime createdAt ) { - public static TemplateDetailInfoResponse toResponse(Form form, TemplateMetadata templateMetadata, List templateQuestionList, List templatePurposeResponseList) { + public static TemplateDetailInfoResponse toResponse(Form form, Optional templateMetadata, List templateQuestionList, List templatePurposeResponseList) { return TemplateDetailInfoResponse.builder() .id(form.getId()) .title(form.getTitle()) .templateName(form.getFormTag().getTag()) - .templateImageUrl(templateMetadata.getTemplateImageUrl()) + .templateImageUrl(templateMetadata.map(TemplateMetadata::getTemplateImageUrl).orElse(null)) .introduction(form.getIntroduction()) - .tipTitle(templateMetadata.getTipTitle()) - .tipDescription(templateMetadata.getTipDescription()) + .tipTitle(templateMetadata.map(TemplateMetadata::getTipTitle).orElse(null)) + .tipDescription(templateMetadata.map(TemplateMetadata::getTipDescription).orElse(null)) .templateDetailQuestionList(templateQuestionList) .templatePurposeResponseList(templatePurposeResponseList) + .createdAt(form.getCreatedAt()) .build(); } } \ No newline at end of file diff --git a/layer-api/src/main/java/org/layer/domain/template/service/TemplateService.java b/layer-api/src/main/java/org/layer/domain/template/service/TemplateService.java index b1eb3783..616a410c 100644 --- a/layer-api/src/main/java/org/layer/domain/template/service/TemplateService.java +++ b/layer-api/src/main/java/org/layer/domain/template/service/TemplateService.java @@ -19,6 +19,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; import static org.layer.domain.form.entity.FormType.TEMPLATE; @@ -42,18 +43,18 @@ public TemplateSimpleInfoResponse getTemplateSimpleInfo(Long formId) { //== 상세 정보 단건 조회 ==// public TemplateDetailInfoResponse getTemplateDetailInfo(Long formId) { Form form = formRepository.findByIdOrThrow(formId); - TemplateMetadata template = templateMetadataRepository.findByFormIdOrThrow(formId); + Optional template = templateMetadataRepository.findByFormId(formId); List questionList = questionRepository.findAllByFormId(formId); List templatePurposeList = templatePurposeRepository.findAllByFormId(formId); List templatePurposeResponses = templatePurposeList.stream().map(TemplatePurposeResponse::toResponse).toList(); List questionDesList = questionList.stream().map(q -> { - QuestionDescription description = questionDescriptionRepository.findByQuestionIdOrThrow(q.getId()); + Optional description = questionDescriptionRepository.findByQuestionId(q.getId()); return TemplateDetailQuestionResponse.builder() .questionId(q.getId()) .question(q.getContent()) - .description(description.getDescription()).build(); + .description(description.map(QuestionDescription::getDescription).orElse(null)).build(); }).toList(); return TemplateDetailInfoResponse.toResponse(form, template, questionDesList, templatePurposeResponses); diff --git a/layer-domain/src/main/java/org/layer/domain/analyze/entity/Analyze.java b/layer-domain/src/main/java/org/layer/domain/analyze/entity/Analyze.java index 04d77388..e2fb8cd8 100644 --- a/layer-domain/src/main/java/org/layer/domain/analyze/entity/Analyze.java +++ b/layer-domain/src/main/java/org/layer/domain/analyze/entity/Analyze.java @@ -1,8 +1,10 @@ package org.layer.domain.analyze.entity; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import org.layer.domain.analyze.enums.AnalyzeDetailType; import org.layer.domain.analyze.enums.AnalyzeType; import jakarta.persistence.CascadeType; @@ -70,4 +72,14 @@ private Analyze(Long retrospectId, Long memberId, int scoreOne, int scoreTwo, in this.analyzeType = analyzeType; this.analyzeDetails = analyzeDetails; } + public AnalyzeDetail getTopCountAnalyzeDetailBy(AnalyzeDetailType analyzeDetailType){ + return analyzeDetails.stream() + .filter(analyzeDetail -> analyzeDetail.getAnalyzeDetailType().equals(analyzeDetailType)) + .max(Comparator.comparing(AnalyzeDetail::getCount)) + .orElse(getEmptyAnalyzeDetail()); + } + + private AnalyzeDetail getEmptyAnalyzeDetail(){ + return AnalyzeDetail.builder().build(); + } } diff --git a/layer-domain/src/main/java/org/layer/domain/analyze/repository/AnalyzeRepository.java b/layer-domain/src/main/java/org/layer/domain/analyze/repository/AnalyzeRepository.java index b73347c0..9833c03e 100644 --- a/layer-domain/src/main/java/org/layer/domain/analyze/repository/AnalyzeRepository.java +++ b/layer-domain/src/main/java/org/layer/domain/analyze/repository/AnalyzeRepository.java @@ -2,12 +2,17 @@ import static org.layer.common.exception.AnalyzeExcepitonType.*; + +import java.util.List; import java.util.Optional; import org.layer.domain.analyze.entity.Analyze; import org.layer.domain.analyze.enums.AnalyzeType; import org.layer.domain.analyze.exception.AnalyzeExcepiton; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + public interface AnalyzeRepository extends JpaRepository { Optional findByRetrospectIdAndAnalyzeType(Long retrospectId, AnalyzeType analyzeType); @@ -23,4 +28,10 @@ default Analyze findByRetrospectIdAndAnalyzeTypeAndMemberIdOrThrow(Long retrospe return findByRetrospectIdAndAnalyzeTypeAndMemberId(retrospectId, analyzeType, memberId) .orElseThrow(() -> new AnalyzeExcepiton(NOT_FOUND_ANALYZE)); } + + @Query("SELECT a FROM Analyze a " + + "JOIN FETCH a.analyzeDetails " + + "WHERE a.memberId = :memberId " + + "AND a.retrospectId IN :retrospectIds") + List findAllByMemberIdAndRetrospectIdInQuery(@Param("memberId") Long memberId, @Param("retrospectIds") List retrospectIds); } diff --git a/layer-domain/src/main/java/org/layer/domain/retrospect/dto/SpaceRetrospectDto.java b/layer-domain/src/main/java/org/layer/domain/retrospect/dto/SpaceRetrospectDto.java new file mode 100644 index 00000000..3a1a9681 --- /dev/null +++ b/layer-domain/src/main/java/org/layer/domain/retrospect/dto/SpaceRetrospectDto.java @@ -0,0 +1,15 @@ +package org.layer.domain.retrospect.dto; + +import org.layer.domain.retrospect.entity.Retrospect; +import org.layer.domain.space.entity.Space; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class SpaceRetrospectDto { + + private final Space space; + private final Retrospect retrospect; +} diff --git a/layer-domain/src/main/java/org/layer/domain/retrospect/repository/RetrospectRepository.java b/layer-domain/src/main/java/org/layer/domain/retrospect/repository/RetrospectRepository.java index b94a2301..07c54d62 100644 --- a/layer-domain/src/main/java/org/layer/domain/retrospect/repository/RetrospectRepository.java +++ b/layer-domain/src/main/java/org/layer/domain/retrospect/repository/RetrospectRepository.java @@ -1,6 +1,7 @@ package org.layer.domain.retrospect.repository; import org.layer.domain.actionItem.dto.MemberActionItemResponse; +import org.layer.domain.retrospect.dto.SpaceRetrospectDto; import org.layer.domain.retrospect.entity.Retrospect; import org.layer.domain.retrospect.entity.RetrospectStatus; import org.layer.domain.retrospect.exception.RetrospectException; @@ -12,6 +13,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import static org.layer.common.exception.RetrospectExceptionType.NOT_FOUND_RETROSPECT; @@ -20,7 +22,6 @@ public interface RetrospectRepository extends JpaRepository, R List findAllByDeadlineAfterAndRetrospectStatus(LocalDateTime now, RetrospectStatus retrospectStatus); - default Retrospect findByIdOrThrow(Long retrospectId) { return findById(retrospectId) .orElseThrow(() -> new RetrospectException(NOT_FOUND_RETROSPECT)); @@ -39,4 +40,15 @@ default Retrospect findByIdOrThrow(Long retrospectId) { "ORDER BY r.deadline DESC") List findAllMemberActionItemResponsesByMemberId(@Param("memberId") Long memberId); + @Query("SELECT new org.layer.domain.retrospect.dto.SpaceRetrospectDto(s, r) " + + "FROM Retrospect r " + + "JOIN Space s ON r.spaceId = s.id " + + "WHERE r.spaceId = :spaceId " + + "AND r.retrospectStatus = :retrospectStatus " + + "AND r.deadline > :twoMonthsAgo " + + "ORDER BY r.deadline ASC " + + "LIMIT 1") + Optional findFirstBySpaceIdAndRetrospectStatusAndDeadlineAfterOrderByDeadline(Long spaceId, + RetrospectStatus retrospectStatus, LocalDateTime twoMonthsAgo); + } diff --git a/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java b/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java index b268ee2e..1c887ddc 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java +++ b/layer-domain/src/main/java/org/layer/domain/space/repository/MemberSpaceRelationRepository.java @@ -5,6 +5,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + import java.util.List; import java.util.Optional; @@ -19,5 +21,6 @@ public interface MemberSpaceRelationRepository extends JpaRepository findAllByMemberId(Long memberId); + @Query("SELECT m FROM MemberSpaceRelation m JOIN FETCH m.space WHERE m.memberId = :memberId") + List findAllByMemberId(@Param("memberId") Long memberId); } diff --git a/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepositoryImpl.java b/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepositoryImpl.java index fb37fa0a..5663f22a 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepositoryImpl.java +++ b/layer-domain/src/main/java/org/layer/domain/space/repository/SpaceRepositoryImpl.java @@ -42,7 +42,6 @@ public List findAllSpacesByMemberIdAndCategoryAndCursor(Lo return getSpaceWithMemberCountQuery() .where(predicate) .groupBy(space.id) - .orderBy(space.createdAt.desc()) .limit(pageSize + 1) .fetch(); } @@ -129,6 +128,7 @@ private JPAQuery getSpaceWithMemberCountQuery() { .leftJoin(memberCountRelationTable).on(space.id.eq(memberCountRelationTable.space.id)) .leftJoin(member).on(space.leaderId.eq(member.id)) .leftJoin(form).on(space.formId.eq(form.id)) + .orderBy(space.createdAt.desc()) .orderBy(form.id.desc()) .limit(1); @@ -158,6 +158,7 @@ private JPAQuery getSpaceWithMemberCountQuery(Long memberI .leftJoin(memberCountRelationTable).on(space.id.eq(memberCountRelationTable.space.id)) .leftJoin(member).on(space.leaderId.eq(member.id)) .leftJoin(form).on(space.formId.eq(form.id)) + .orderBy(space.createdAt.desc()) .orderBy(form.id.desc()) .limit(1); } diff --git a/layer-domain/src/main/java/org/layer/domain/template/exception/TemplateExceptionType.java b/layer-domain/src/main/java/org/layer/domain/template/exception/TemplateExceptionType.java index 52757781..3c79ac57 100644 --- a/layer-domain/src/main/java/org/layer/domain/template/exception/TemplateExceptionType.java +++ b/layer-domain/src/main/java/org/layer/domain/template/exception/TemplateExceptionType.java @@ -15,11 +15,11 @@ public enum TemplateExceptionType implements ExceptionType { @Override public HttpStatus httpStatus() { - return null; + return status; } @Override public String message() { - return null; + return message; } }