diff --git a/src/docs/asciidoc/Team-API.adoc b/src/docs/asciidoc/Team-API.adoc index 0fe3436e..32e55c69 100644 --- a/src/docs/asciidoc/Team-API.adoc +++ b/src/docs/asciidoc/Team-API.adoc @@ -37,6 +37,10 @@ operation::team-controller-test/sign-in_team[snippets='http-request,path-paramet === Team 소모임 조회 operation::team-controller-test/get_team[snippets='http-request,http-response,response-fields'] +[[Team-목표보드-조회]] +=== Team 목표보드_소모임 단건_조회 +operation::team-controller-test/get_team_detail[snippets='http-request,path-parameters,http-response,response-fields'] + [[Team-소모임-수정]] === Team 소모임 수정 operation::team-controller-test/update_team[snippets='http-request,path-parameters,http-response,request-fields,response-fields'] diff --git a/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepository.java b/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepository.java index 10d5ffbd..ec8f67d9 100644 --- a/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepository.java +++ b/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepository.java @@ -3,5 +3,6 @@ import com.moing.backend.domain.board.application.dto.response.GetAllBoardResponse; public interface BoardCustomRepository { - GetAllBoardResponse findBoardAll(Long teamId, Long boardId); + GetAllBoardResponse findBoardAll(Long teamId, Long memberId); + Integer findUnReadBoardNum(Long teamId, Long memberId); } diff --git a/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepositoryImpl.java b/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepositoryImpl.java index 2532ea09..8a3001a5 100644 --- a/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepositoryImpl.java +++ b/src/main/java/com/moing/backend/domain/board/domain/repository/BoardCustomRepositoryImpl.java @@ -13,9 +13,9 @@ import static com.moing.backend.domain.board.domain.entity.QBoard.board; import static com.moing.backend.domain.boardRead.domain.entity.QBoardRead.boardRead; -public class BoardCustomRepositoryImpl implements BoardCustomRepository { - private final JPAQueryFactory queryFactory; +public class BoardCustomRepositoryImpl implements BoardCustomRepository { + private final JPAQueryFactory queryFactory; public BoardCustomRepositoryImpl(EntityManager em) { this.queryFactory = new JPAQueryFactory(em); } @@ -65,4 +65,22 @@ public GetAllBoardResponse findBoardAll(Long teamId, Long memberId) { return new GetAllBoardResponse(noticeBlocks.size(), noticeBlocks, regularBlocks.size(), regularBlocks); } + + @Override + public Integer findUnReadBoardNum(Long teamId, Long memberId) { + // 전체 게시글 수 + Long allBoards = queryFactory + .selectFrom(board) + .where(board.team.teamId.eq(teamId)) + .fetchCount(); + + // 멤버가 읽은 게시글 수 + Long readBoards = queryFactory + .selectFrom(boardRead) + .where(boardRead.member.memberId.eq(memberId)) + .where(boardRead.board.team.teamId.eq(teamId)) + .fetchCount(); + + return Math.toIntExact(allBoards - readBoards); + } } diff --git a/src/main/java/com/moing/backend/domain/board/domain/service/BoardGetService.java b/src/main/java/com/moing/backend/domain/board/domain/service/BoardGetService.java index e5db6029..2a20429d 100644 --- a/src/main/java/com/moing/backend/domain/board/domain/service/BoardGetService.java +++ b/src/main/java/com/moing/backend/domain/board/domain/service/BoardGetService.java @@ -25,4 +25,8 @@ public Board getBoard(Long boardId){ public GetAllBoardResponse getBoardAll(Long teamId, Long memberId){ return boardRepository.findBoardAll(teamId, memberId); } + + public Integer getUnReadBoardNum(Long teamId, Long memberId){ + return boardRepository.findUnReadBoardNum(teamId, memberId); + } } diff --git a/src/main/java/com/moing/backend/domain/member/domain/entity/Member.java b/src/main/java/com/moing/backend/domain/member/domain/entity/Member.java index ad9370f8..06bef64f 100644 --- a/src/main/java/com/moing/backend/domain/member/domain/entity/Member.java +++ b/src/main/java/com/moing/backend/domain/member/domain/entity/Member.java @@ -47,7 +47,7 @@ public class Member extends BaseTimeEntity { @Column(nullable = false, unique = true) private String email; - private String profileImage; //없으면 undef + private String profileImage; @Column(length = 10) @Enumerated(EnumType.STRING) @@ -156,11 +156,4 @@ public Member(LocalDate birthDate, String email, String fcmToken, Gender gender, this.socialId = socialId; } - public void deleteTeamMember(){ - List teamMemberList=this.getTeamMembers(); - for(TeamMember teamMember:teamMemberList){ - teamMember.deleteMember(); - } - } - } diff --git a/src/main/java/com/moing/backend/domain/team/application/dto/response/DeleteTeamResponse.java b/src/main/java/com/moing/backend/domain/team/application/dto/response/DeleteTeamResponse.java index 92ed9017..8cc78bb2 100644 --- a/src/main/java/com/moing/backend/domain/team/application/dto/response/DeleteTeamResponse.java +++ b/src/main/java/com/moing/backend/domain/team/application/dto/response/DeleteTeamResponse.java @@ -11,4 +11,9 @@ @NoArgsConstructor public class DeleteTeamResponse { private Long teamId; + private String teamName; + private Integer numOfMember; + private Long duration; //걸린시간(단위:날짜) + private Long numOfMission; + private Integer levelOfFire; //불꽃 레벨 } diff --git a/src/main/java/com/moing/backend/domain/team/application/dto/response/GetTeamDetailResponse.java b/src/main/java/com/moing/backend/domain/team/application/dto/response/GetTeamDetailResponse.java new file mode 100644 index 00000000..63af6dcb --- /dev/null +++ b/src/main/java/com/moing/backend/domain/team/application/dto/response/GetTeamDetailResponse.java @@ -0,0 +1,18 @@ +package com.moing.backend.domain.team.application.dto.response; + +import com.moing.backend.domain.team.domain.constant.Category; +import com.querydsl.core.annotations.QueryProjection; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Builder +@AllArgsConstructor +public class GetTeamDetailResponse { + private Integer boardNum; //안 읽은 게시글 + private TeamInfo teamInfo; +} diff --git a/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamBlock.java b/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamBlock.java index 763d9248..c4988a47 100644 --- a/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamBlock.java +++ b/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamBlock.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -24,9 +25,10 @@ public class TeamBlock { private Integer numOfMember; private String category; private String startDate; + private LocalDateTime deletionTime; @QueryProjection - public TeamBlock(Long teamId, LocalDateTime approvalTime, Integer levelOfFire, String teamName, Integer numOfMember, Category category){ + public TeamBlock(Long teamId, LocalDateTime approvalTime, Integer levelOfFire, String teamName, Integer numOfMember, Category category, LocalDateTime deletionTime){ this.teamId=teamId; this.duration=calculateDuration(approvalTime); this.levelOfFire=levelOfFire; @@ -34,13 +36,18 @@ public TeamBlock(Long teamId, LocalDateTime approvalTime, Integer levelOfFire, S this.numOfMember=numOfMember; this.category=category.getMessage(); this.startDate=approvalTime.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + this.deletionTime=deletionTime; } - private Long calculateDuration(LocalDateTime approvalTime) { + public Long calculateDuration(LocalDateTime approvalTime) { ZoneId seoulZoneId = ZoneId.of("Asia/Seoul"); - LocalDateTime currentSeoulTime = LocalDateTime.now(seoulZoneId).withSecond(0).withNano(0); - LocalDateTime adjustedApprovalTime = approvalTime.withSecond(0).withNano(0); - return ChronoUnit.DAYS.between(adjustedApprovalTime, currentSeoulTime); + LocalDateTime currentDateTime = LocalDateTime.now(seoulZoneId); + + long hoursBetween = ChronoUnit.HOURS.between(approvalTime, currentDateTime); + long daysBetween = hoursBetween / 24; + + return daysBetween; } + } diff --git a/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamInfo.java b/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamInfo.java new file mode 100644 index 00000000..77dc2c8c --- /dev/null +++ b/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamInfo.java @@ -0,0 +1,23 @@ +package com.moing.backend.domain.team.application.dto.response; + +import com.moing.backend.domain.team.domain.constant.Category; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +@Getter +@Builder +public class TeamInfo { + private Boolean isDeleted; + private LocalDateTime deletionTime; + private String teamName; //소모임 이름 + private Integer numOfMember; //소모임원 수 + private Category category; //카테고리 + private String introduction; //소개 + private List teamMemberInfoList = new ArrayList<>(); //소모임원 정보 +} \ No newline at end of file diff --git a/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamMemberInfo.java b/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamMemberInfo.java new file mode 100644 index 00000000..cc42df33 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/team/application/dto/response/TeamMemberInfo.java @@ -0,0 +1,26 @@ +package com.moing.backend.domain.team.application.dto.response; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class TeamMemberInfo { + private Long memberId; + private String nickName; + private String profileImage; + private String introduction; + private Boolean isLeader; + + @QueryProjection + public TeamMemberInfo(Long memberId, String nickName, String profileImage, String introduction, Long leaderId){ + this.memberId=memberId; + this.nickName=nickName; + this.profileImage=profileImage; + this.introduction=introduction; + this.isLeader=memberId.equals(leaderId); + } +} diff --git a/src/main/java/com/moing/backend/domain/team/application/mapper/TeamMapper.java b/src/main/java/com/moing/backend/domain/team/application/mapper/TeamMapper.java index 327adba1..ec4ba6ac 100644 --- a/src/main/java/com/moing/backend/domain/team/application/mapper/TeamMapper.java +++ b/src/main/java/com/moing/backend/domain/team/application/mapper/TeamMapper.java @@ -2,11 +2,21 @@ import com.moing.backend.domain.member.domain.entity.Member; import com.moing.backend.domain.team.application.dto.request.CreateTeamRequest; +import com.moing.backend.domain.team.application.dto.response.DeleteTeamResponse; +import com.moing.backend.domain.team.application.dto.response.GetTeamDetailResponse; +import com.moing.backend.domain.team.application.dto.response.TeamInfo; +import com.moing.backend.domain.team.application.dto.response.TeamMemberInfo; import com.moing.backend.domain.team.domain.constant.ApprovalStatus; import com.moing.backend.domain.team.domain.constant.Category; import com.moing.backend.domain.team.domain.entity.Team; import org.springframework.stereotype.Component; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.List; + @Component public class TeamMapper { @@ -23,4 +33,34 @@ public Team createTeam(CreateTeamRequest createTeamRequest, Member member) { .levelOfFire(1) .build(); } + + public GetTeamDetailResponse toTeamDetailResponse(Team team, Integer boardNum, List teamMemberInfoList) { + TeamInfo teamInfo = new TeamInfo(team.isDeleted(), team.getDeletionTime(), team.getName(), teamMemberInfoList.size(), team.getCategory(), team.getIntroduction(), teamMemberInfoList); + return GetTeamDetailResponse.builder() + .boardNum(boardNum) + .teamInfo(teamInfo) + .build(); + } + + public DeleteTeamResponse toDeleteTeamResponse(Long numOfMission, Team team){ + return DeleteTeamResponse.builder() + .teamId(team.getTeamId()) + .teamName(team.getName()) + .numOfMember(team.getNumOfMember()) + .levelOfFire(team.getLevelOfFire()) + .duration(calculateDuration(team.getApprovalTime())) + .numOfMission(numOfMission) + .build(); + } + + public Long calculateDuration(LocalDateTime approvalTime) { + ZoneId seoulZoneId = ZoneId.of("Asia/Seoul"); + LocalDateTime currentDateTime = LocalDateTime.now(seoulZoneId); + + long hoursBetween = ChronoUnit.HOURS.between(approvalTime, currentDateTime); + long daysBetween = hoursBetween / 24; + + return daysBetween; + } + } diff --git a/src/main/java/com/moing/backend/domain/team/application/service/CheckLeaderUserCase.java b/src/main/java/com/moing/backend/domain/team/application/service/CheckLeaderUserCase.java index 616b0d31..b397a9e9 100644 --- a/src/main/java/com/moing/backend/domain/team/application/service/CheckLeaderUserCase.java +++ b/src/main/java/com/moing/backend/domain/team/application/service/CheckLeaderUserCase.java @@ -6,16 +6,13 @@ import org.springframework.stereotype.Service; import javax.transaction.Transactional; +import java.util.Objects; @Service @Transactional @RequiredArgsConstructor public class CheckLeaderUserCase { public boolean isTeamLeader(Member member, Team team) { - if (member.getMemberId() == team.getLeaderId()) { - return true; - } else { - return false; - } + return Objects.equals(member.getMemberId(), team.getLeaderId()); } } diff --git a/src/main/java/com/moing/backend/domain/team/application/service/DisbandTeamUserCase.java b/src/main/java/com/moing/backend/domain/team/application/service/DisbandTeamUserCase.java index 77ef8c86..f0e88b2e 100644 --- a/src/main/java/com/moing/backend/domain/team/application/service/DisbandTeamUserCase.java +++ b/src/main/java/com/moing/backend/domain/team/application/service/DisbandTeamUserCase.java @@ -2,12 +2,12 @@ import com.moing.backend.domain.member.domain.entity.Member; import com.moing.backend.domain.member.domain.service.MemberGetService; +import com.moing.backend.domain.mission.domain.service.MissionQueryService; import com.moing.backend.domain.team.application.dto.response.DeleteTeamResponse; +import com.moing.backend.domain.team.application.mapper.TeamMapper; import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.team.domain.service.TeamGetService; import com.moing.backend.domain.team.exception.NotAuthByTeamException; -import com.moing.backend.domain.teamMember.domain.entity.TeamMember; -import com.moing.backend.domain.teamMember.domain.service.TeamMemberGetService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,18 +21,18 @@ public class DisbandTeamUserCase { private final MemberGetService memberGetService; private final TeamGetService teamGetService; private final CheckLeaderUserCase checkLeaderUserCase; - private final TeamMemberGetService teamMemberGetService; + private final MissionQueryService missionQueryService; + private final TeamMapper teamMapper; public DeleteTeamResponse disbandTeam(String socialId, Long teamId) { Member member = memberGetService.getMemberBySocialId(socialId); Team team = teamGetService.getTeamByTeamId(teamId); if (checkLeaderUserCase.isTeamLeader(member, team)) { - //TODO: 팀의 모든 멤버 isDeleted true 해주환 team.deleteTeam(); } else { throw new NotAuthByTeamException(); } - return new DeleteTeamResponse(team.getTeamId()); + return teamMapper.toDeleteTeamResponse(missionQueryService.findMissionsCountByTeam(team.getTeamId()), team); } } diff --git a/src/main/java/com/moing/backend/domain/team/application/service/GetTeamUserCase.java b/src/main/java/com/moing/backend/domain/team/application/service/GetTeamUserCase.java index 56ba881a..30266ad4 100644 --- a/src/main/java/com/moing/backend/domain/team/application/service/GetTeamUserCase.java +++ b/src/main/java/com/moing/backend/domain/team/application/service/GetTeamUserCase.java @@ -1,13 +1,20 @@ package com.moing.backend.domain.team.application.service; +import com.moing.backend.domain.board.domain.service.BoardGetService; import com.moing.backend.domain.member.domain.entity.Member; import com.moing.backend.domain.member.domain.service.MemberGetService; +import com.moing.backend.domain.team.application.dto.response.GetTeamDetailResponse; import com.moing.backend.domain.team.application.dto.response.GetTeamResponse; +import com.moing.backend.domain.team.application.dto.response.TeamMemberInfo; +import com.moing.backend.domain.team.application.mapper.TeamMapper; +import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.team.domain.service.TeamGetService; +import com.moing.backend.domain.teamMember.domain.service.TeamMemberGetService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; +import java.util.List; @Service @Transactional @@ -15,9 +22,20 @@ public class GetTeamUserCase { private final MemberGetService memberGetService; private final TeamGetService teamGetService; + private final BoardGetService boardGetService; + private final TeamMemberGetService teamMemberGetService; + private final TeamMapper teamMapper; public GetTeamResponse getTeam(String socialId) { Member member = memberGetService.getMemberBySocialId(socialId); return teamGetService.getTeamByMember(member); } + + public GetTeamDetailResponse getTeamDetailResponse(String socialId, Long teamId) { + Member member = memberGetService.getMemberBySocialId(socialId); + Integer boardNum = boardGetService.getUnReadBoardNum(teamId, member.getMemberId()); + List teamMemberInfoList = teamMemberGetService.getTeamMemberInfo(teamId); + Team team = teamGetService.getTeamByTeamId(teamId); + return teamMapper.toTeamDetailResponse(team, boardNum, teamMemberInfoList); + } } diff --git a/src/main/java/com/moing/backend/domain/team/application/service/WithdrawTeamUserCase.java b/src/main/java/com/moing/backend/domain/team/application/service/WithdrawTeamUserCase.java index 46c42dfa..15538b62 100644 --- a/src/main/java/com/moing/backend/domain/team/application/service/WithdrawTeamUserCase.java +++ b/src/main/java/com/moing/backend/domain/team/application/service/WithdrawTeamUserCase.java @@ -2,7 +2,9 @@ import com.moing.backend.domain.member.domain.entity.Member; import com.moing.backend.domain.member.domain.service.MemberGetService; +import com.moing.backend.domain.mission.domain.service.MissionQueryService; import com.moing.backend.domain.team.application.dto.response.DeleteTeamResponse; +import com.moing.backend.domain.team.application.mapper.TeamMapper; import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.team.domain.service.TeamGetService; import com.moing.backend.domain.teamMember.domain.entity.TeamMember; @@ -20,12 +22,14 @@ public class WithdrawTeamUserCase { private final TeamMemberGetService teamMemberGetService; private final MemberGetService memberGetService; private final TeamGetService teamGetService; + private final MissionQueryService missionQueryService; + private final TeamMapper teamMapper; public DeleteTeamResponse withdrawTeam(String socialId, Long teamId) { Member member = memberGetService.getMemberBySocialId(socialId); Team team = teamGetService.getTeamByTeamId(teamId); TeamMember teamMember = teamMemberGetService.getTeamMember(member, team); - teamMember.deleteMember(); - return new DeleteTeamResponse(team.getTeamId()); + teamMember.deleteMember(team); + return teamMapper.toDeleteTeamResponse(missionQueryService.findMissionsCountByTeam(team.getTeamId()),team); } } diff --git a/src/main/java/com/moing/backend/domain/team/domain/entity/Team.java b/src/main/java/com/moing/backend/domain/team/domain/entity/Team.java index 1b4f531e..a028794e 100644 --- a/src/main/java/com/moing/backend/domain/team/domain/entity/Team.java +++ b/src/main/java/com/moing/backend/domain/team/domain/entity/Team.java @@ -80,11 +80,15 @@ public void updateTeam(String name, String introduction, String profileImgUrl) { } public void deleteTeam() { - this.isDeleted = true; + this.isDeleted=true; this.deletionTime = LocalDateTime.now(ZoneId.of("Asia/Seoul")).withNano(0); } public void addTeamMember(){ numOfMember++; } + + public void deleteTeamMember(){ + numOfMember--; + } } diff --git a/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepository.java b/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepository.java index 3a2921d4..fdbd1a88 100644 --- a/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepository.java +++ b/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepository.java @@ -1,5 +1,6 @@ package com.moing.backend.domain.team.domain.repository; +import com.moing.backend.domain.team.application.dto.response.GetTeamDetailResponse; import com.moing.backend.domain.team.application.dto.response.GetTeamResponse; import com.moing.backend.domain.team.domain.entity.Team; diff --git a/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepositoryImpl.java b/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepositoryImpl.java index 21664917..be1b15ab 100644 --- a/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepositoryImpl.java +++ b/src/main/java/com/moing/backend/domain/team/domain/repository/TeamCustomRepositoryImpl.java @@ -1,13 +1,15 @@ package com.moing.backend.domain.team.domain.repository; -import com.moing.backend.domain.team.application.dto.response.GetTeamResponse; -import com.moing.backend.domain.team.application.dto.response.QTeamBlock; -import com.moing.backend.domain.team.application.dto.response.TeamBlock; +import com.moing.backend.domain.member.domain.entity.QMember; +import com.moing.backend.domain.team.application.dto.response.*; import com.moing.backend.domain.team.domain.constant.ApprovalStatus; import com.moing.backend.domain.team.domain.entity.Team; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import javax.persistence.EntityManager; +import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -31,39 +33,50 @@ public GetTeamResponse findTeamByMemberId(Long memberId) { @Override public Optional findTeamByTeamId(Long teamId) { + LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); + return Optional.ofNullable(queryFactory.selectFrom(team) .where(team.teamId.eq(teamId)) - .where(team.isDeleted.eq(false)) + .where(team.isDeleted.eq(false) // 강제종료되지 않았거나 + .or(team.deletionTime.after(threeDaysAgo))) // 강제종료된 경우 3일이 지나지 않았다면 .fetchOne()); } @Override public List findTeamIdByMemberId(Long memberId){ + LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); + return queryFactory .select(team.teamId) .from(teamMember) .innerJoin(teamMember.team, team) .on(teamMember.member.memberId.eq(memberId)) - .where(team.approvalStatus.eq(ApprovalStatus.APPROVAL)) //승인 되었고 - .where(team.isDeleted.eq(false)) //강제종료되었는지 - .where(teamMember.isDeleted.eq(false)) //탈퇴했는지 + .where(team.approvalStatus.eq(ApprovalStatus.APPROVAL)) // 승인 되었고 + .where(teamMember.isDeleted.eq(false)) // 탈퇴하지 않았다면 + .where(team.isDeleted.eq(false) // 강제종료되지 않았거나 + .or(team.deletionTime.after(threeDaysAgo))) // 강제종료된 경우 3일이 지나지 않았다면 .orderBy(team.approvalTime.asc()) .fetch(); } + private List getTeamBlock(Long memberId) { + LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); + return queryFactory .select(new QTeamBlock(team.teamId, team.approvalTime, team.levelOfFire, team.name, team.numOfMember, - team.category)) + team.category, + team.deletionTime)) .from(teamMember) .innerJoin(teamMember.team, team) .on(teamMember.member.memberId.eq(memberId)) - .where(team.approvalStatus.eq(ApprovalStatus.APPROVAL)) //승인 되었고 - .where(team.isDeleted.eq(false)) //강제종료되었는지 - .where(teamMember.isDeleted.eq(false)) //탈퇴했는지 + .where(teamMember.isDeleted.eq(false)) // 탈퇴하지 않았다면 + .where(team.isDeleted.eq(false) // 강제종료되지 않았거나 + .or(team.deletionTime.isNotNull() + .or(team.deletionTime.after(threeDaysAgo)))) // 강제종료된 경우 3일이 지나지 않았다면 .orderBy(team.approvalTime.asc()) .fetch(); } diff --git a/src/main/java/com/moing/backend/domain/team/presentation/TeamController.java b/src/main/java/com/moing/backend/domain/team/presentation/TeamController.java index 624e2615..907fef05 100644 --- a/src/main/java/com/moing/backend/domain/team/presentation/TeamController.java +++ b/src/main/java/com/moing/backend/domain/team/presentation/TeamController.java @@ -2,15 +2,11 @@ import com.moing.backend.domain.team.application.dto.request.CreateTeamRequest; import com.moing.backend.domain.team.application.dto.request.UpdateTeamRequest; -import com.moing.backend.domain.team.application.dto.response.CreateTeamResponse; -import com.moing.backend.domain.team.application.dto.response.DeleteTeamResponse; -import com.moing.backend.domain.team.application.dto.response.GetTeamResponse; -import com.moing.backend.domain.team.application.dto.response.UpdateTeamResponse; +import com.moing.backend.domain.team.application.dto.response.*; import com.moing.backend.domain.team.application.service.*; import com.moing.backend.global.config.security.dto.User; import com.moing.backend.global.response.SuccessResponse; import lombok.AllArgsConstructor; -import org.checkerframework.checker.units.qual.A; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -42,7 +38,7 @@ public ResponseEntity> createTeam(@Authentic } /** - * 소모임 조회하기 (소모임 홈화면) : 인증사진 제외 + * 소모임 전체 조회하기 (소모임 홈화면) : 인증사진 제외 * [GET] api/team * 작성자 : 김민수 */ @@ -51,6 +47,17 @@ public ResponseEntity> getTeam(@AuthenticationP return ResponseEntity.ok(SuccessResponse.create(GET_TEAM_SUCCESS.getMessage(), this.getTeamUserCase.getTeam(user.getSocialId()))); } + /** + * 소모임 하나만 조회하기 (목표보드) : 소모임 레벨, 상태 바 제외 + * [GET] api/team/{teamId} + * 작성자: 김민수 + */ + @GetMapping("/{teamId}") + public ResponseEntity> getTeamDetail(@AuthenticationPrincipal User user, + @PathVariable Long teamId) { + return ResponseEntity.ok(SuccessResponse.create(GET_TEAM_DETAIL_SUCCESS.getMessage(), this.getTeamUserCase.getTeamDetailResponse(user.getSocialId(), teamId))); + } + /** * 소모임 가입하기 (소모임원으로 입장) * [POST] api/team/{teamId} diff --git a/src/main/java/com/moing/backend/domain/team/presentation/constant/TeamResponseMessage.java b/src/main/java/com/moing/backend/domain/team/presentation/constant/TeamResponseMessage.java index 2ed8320a..d9d08180 100644 --- a/src/main/java/com/moing/backend/domain/team/presentation/constant/TeamResponseMessage.java +++ b/src/main/java/com/moing/backend/domain/team/presentation/constant/TeamResponseMessage.java @@ -8,6 +8,7 @@ public enum TeamResponseMessage { CREATE_TEAM_SUCCESS("소모임을 생성하였습니다"), GET_TEAM_SUCCESS("홈 화면에서 내 소모임을 모두 조회했습니다."), + GET_TEAM_DETAIL_SUCCESS("목표보드를 조회했습니다"), SIGNIN_TEAM_SUCCESS("소모임에 가입하였습니다"), DISBAND_TEAM_SUCCESS("[소모임장 권한] 소모임을 강제 종료했습니다."), UPDATE_TEAM_SUCCESS("[소모임장 권한] 소모임을 수정했습니다"), diff --git a/src/main/java/com/moing/backend/domain/teamMember/domain/entity/TeamMember.java b/src/main/java/com/moing/backend/domain/teamMember/domain/entity/TeamMember.java index c30cf2ed..9769d51f 100644 --- a/src/main/java/com/moing/backend/domain/teamMember/domain/entity/TeamMember.java +++ b/src/main/java/com/moing/backend/domain/teamMember/domain/entity/TeamMember.java @@ -42,7 +42,8 @@ public void updateMember(Member member) { member.getTeamMembers().add(this); } - public void deleteMember() { + public void deleteMember(Team team) { this.isDeleted = true; + team.deleteTeamMember(); } } diff --git a/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepository.java b/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepository.java index 725b70e1..fdfc1193 100644 --- a/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepository.java +++ b/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepository.java @@ -1,5 +1,7 @@ package com.moing.backend.domain.teamMember.domain.repository; +import com.moing.backend.domain.team.application.dto.response.TeamMemberInfo; + import java.util.List; import java.util.Optional; @@ -7,4 +9,5 @@ public interface TeamMemberCustomRepository { List findMemberIdsByTeamId(Long teamId); Optional> findFcmTokensByTeamIdAndMemberId(Long teamId, Long memberId); Optional> findFcmTokensByTeamId(Long teamId); + List findTeamMemberInfoByTeamId(Long teamId); } diff --git a/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepositoryImpl.java b/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepositoryImpl.java index 40f64eaa..cd5cbaf2 100644 --- a/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepositoryImpl.java +++ b/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberCustomRepositoryImpl.java @@ -1,5 +1,8 @@ package com.moing.backend.domain.teamMember.domain.repository; +import com.moing.backend.domain.team.application.dto.response.QTeamMemberInfo; +import com.moing.backend.domain.team.application.dto.response.TeamMemberInfo; +import com.moing.backend.domain.team.domain.constant.ApprovalStatus; import com.moing.backend.domain.team.domain.entity.QTeam; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -7,6 +10,7 @@ import java.util.List; import java.util.Optional; +import static com.moing.backend.domain.team.domain.entity.QTeam.team; import static com.moing.backend.domain.teamMember.domain.entity.QTeamMember.teamMember; public class TeamMemberCustomRepositoryImpl implements TeamMemberCustomRepository { @@ -21,27 +25,47 @@ public List findMemberIdsByTeamId(Long teamId) { return queryFactory .select(teamMember.member.memberId) .from(teamMember) - .where(teamMember.team.teamId.eq(teamId)) - .where(teamMember.team.isDeleted.eq(false)) + .where(teamMember.team.teamId.eq(teamId) + .and(teamMember.team.isDeleted.eq(false))) .fetch(); } @Override public Optional> findFcmTokensByTeamIdAndMemberId(Long teamId, Long memberId) { - return Optional.ofNullable(queryFactory.select(teamMember.member.fcmToken) + List result = queryFactory.select(teamMember.member.fcmToken) .from(teamMember) - .where(teamMember.team.teamId.eq(teamId)) //해당 소모임에 참여하고 있고 - .where(teamMember.member.isNewUploadPush.eq(true)) //알림 설정 on해 있고 - .where(teamMember.member.memberId.ne(memberId)) //지금 유저가 아닌 경우 - .fetch()); + .where(teamMember.team.teamId.eq(teamId) + .and(teamMember.member.isNewUploadPush.eq(true)) + .and(teamMember.member.memberId.ne(memberId))) + .fetch(); + + return result.isEmpty() ? Optional.empty() : Optional.of(result); } @Override public Optional> findFcmTokensByTeamId(Long teamId) { - return Optional.ofNullable(queryFactory.select(teamMember.member.fcmToken) + List result = queryFactory.select(teamMember.member.fcmToken) + .from(teamMember) + .where(teamMember.team.teamId.eq(teamId) + .and(teamMember.member.isNewUploadPush.eq(true))) + .fetch(); + + return result.isEmpty() ? Optional.empty() : Optional.of(result); + } + + @Override + public List findTeamMemberInfoByTeamId(Long teamId){ + return queryFactory + .select(new QTeamMemberInfo( + teamMember.member.memberId, + teamMember.member.nickName, + teamMember.member.profileImage, + teamMember.member.introduction, + teamMember.team.leaderId)) .from(teamMember) - .where(teamMember.team.teamId.eq(teamId)) //해당 소모임에 참여하고 있고 - .where(teamMember.member.isNewUploadPush.eq(true)) //알림 설정 on해 있고 - .fetch()); + .innerJoin(teamMember.team, team) // innerJoin을 사용하여 최적화 + .where(teamMember.team.teamId.eq(teamId) // where 절을 하나로 합침 + .and(teamMember.isDeleted.eq(false))) + .fetch(); } } diff --git a/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberRepository.java b/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberRepository.java index 6bcc3808..42d665b9 100644 --- a/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberRepository.java +++ b/src/main/java/com/moing/backend/domain/teamMember/domain/repository/TeamMemberRepository.java @@ -4,10 +4,13 @@ import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.teamMember.domain.entity.TeamMember; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import javax.swing.text.html.Option; import java.util.Optional; public interface TeamMemberRepository extends JpaRepository, TeamMemberCustomRepository{ Optional findTeamMemberByTeamAndMember(Team team, Member member); + } diff --git a/src/main/java/com/moing/backend/domain/teamMember/domain/service/TeamMemberGetService.java b/src/main/java/com/moing/backend/domain/teamMember/domain/service/TeamMemberGetService.java index 6ba8f3db..ba86c2f7 100644 --- a/src/main/java/com/moing/backend/domain/teamMember/domain/service/TeamMemberGetService.java +++ b/src/main/java/com/moing/backend/domain/teamMember/domain/service/TeamMemberGetService.java @@ -1,6 +1,7 @@ package com.moing.backend.domain.teamMember.domain.service; import com.moing.backend.domain.member.domain.entity.Member; +import com.moing.backend.domain.team.application.dto.response.TeamMemberInfo; import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.team.exception.NotFoundByTeamIdException; import com.moing.backend.domain.teamMember.domain.entity.TeamMember; @@ -32,4 +33,7 @@ public Optional> getFcmTokens(Long teamId) { return teamMemberRepository.findFcmTokensByTeamId(teamId); } + public List getTeamMemberInfo(Long teamId){ + return teamMemberRepository.findTeamMemberInfoByTeamId(teamId); + } } diff --git a/src/test/java/com/moing/backend/domain/team/presentation/TeamControllerTest.java b/src/test/java/com/moing/backend/domain/team/presentation/TeamControllerTest.java index 0b96c84d..615b0557 100644 --- a/src/test/java/com/moing/backend/domain/team/presentation/TeamControllerTest.java +++ b/src/test/java/com/moing/backend/domain/team/presentation/TeamControllerTest.java @@ -5,6 +5,7 @@ import com.moing.backend.domain.team.application.dto.request.UpdateTeamRequest; import com.moing.backend.domain.team.application.dto.response.*; import com.moing.backend.domain.team.application.service.*; +import com.moing.backend.domain.team.domain.constant.Category; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -12,6 +13,7 @@ import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.test.web.servlet.ResultActions; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -107,6 +109,7 @@ public void get_team() throws Exception { .numOfMember(10) .category("ETC") .startDate("2023.09.05") + .deletionTime(LocalDateTime.now().withNano(0)) .build(); TeamBlock teamBlock2=TeamBlock.builder() @@ -117,6 +120,7 @@ public void get_team() throws Exception { .numOfMember(8) .category("SPORTS") .startDate("2023.09.01") + .deletionTime(LocalDateTime.now().withNano(0)) .build(); teamBlocks.add(teamBlock1); @@ -156,9 +160,84 @@ public void get_team() throws Exception { fieldWithPath("data.teamBlocks[0].duration").description("소모임과 함께한 시간"), fieldWithPath("data.teamBlocks[0].levelOfFire").description("불꽃 레벨"), fieldWithPath("data.teamBlocks[0].teamName").description("소모임 이름"), - fieldWithPath("data.teamBlocks[0].numOfMember").description("소모임원 숫자"), + fieldWithPath("data.teamBlocks[0].numOfMember").description("소모임원 명 수"), fieldWithPath("data.teamBlocks[0].category").description("소모임 카테고리"), - fieldWithPath("data.teamBlocks[0].startDate").description("소모임 시작일") + fieldWithPath("data.teamBlocks[0].startDate").description("소모임 시작일"), + fieldWithPath("data.teamBlocks[0].deletionTime").description("소모임 삭제 시간 (삭제 안했으면 null)") + ) + + ) + ); + } + + @Test + public void get_team_detail() throws Exception { + //given + Long teamId = 1L; + List teamMemberInfoList = new ArrayList<>(); + TeamMemberInfo teamMemberInfo = TeamMemberInfo.builder() + .memberId(1L) + .nickName("소모임원 닉네임") + .profileImage("소모임원 프로필 사진") + .introduction("소모임원 소개") + .isLeader(true) + .build(); + teamMemberInfoList.add(teamMemberInfo); + + TeamInfo teamInfo = TeamInfo + .builder() + .isDeleted(true) + .deletionTime(LocalDateTime.now()) + .teamName("소모임 이름") + .numOfMember(1) + .category(Category.ETC) + .introduction("소모임 소개글") + .teamMemberInfoList(teamMemberInfoList).build(); + + GetTeamDetailResponse output = GetTeamDetailResponse + .builder() + .boardNum(1) + .teamInfo(teamInfo) + .build(); + + + given(getTeamUserCase.getTeamDetailResponse(any(),any())).willReturn(output); + + + //when + ResultActions actions = mockMvc.perform(RestDocumentationRequestBuilders. + get("/api/team/{teamId}", teamId) + .header("Authorization", "Bearer ACCESS_TOKEN") + .contentType(MediaType.APPLICATION_JSON) + ); + + + //then + actions + .andExpect(status().isOk()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName("Authorization").description("접근 토큰") + ), + pathParameters( + parameterWithName("teamId").description("팀 아이디") + ), + responseFields( + fieldWithPath("isSuccess").description("true"), + fieldWithPath("message").description("목표보드를 조회했습니다."), + fieldWithPath("data.boardNum").description("안 읽은 공지/게시글"), + fieldWithPath("data.teamInfo.teamName").description("소모임 이름"), + fieldWithPath("data.teamInfo.numOfMember").description("모임원 명 수"), + fieldWithPath("data.teamInfo.category").description("카테고리"), + fieldWithPath("data.teamInfo.introduction").description("모임 소개"), + fieldWithPath("data.teamInfo.isDeleted").description("삭제여부"), + fieldWithPath("data.teamInfo.deletionTime").description("삭제 시간 (삭제 안했으면 null)"), + fieldWithPath("data.teamInfo.teamMemberInfoList[0].memberId").description("유저 아이디"), + fieldWithPath("data.teamInfo.teamMemberInfoList[0].nickName").description("유저 닉네임"), + fieldWithPath("data.teamInfo.teamMemberInfoList[0].profileImage").description("유저 프로필 이미지"), + fieldWithPath("data.teamInfo.teamMemberInfoList[0].introduction").description("유저 소개"), + fieldWithPath("data.teamInfo.teamMemberInfoList[0].isLeader").description("유저 소모임장 여부") ) ) @@ -171,6 +250,11 @@ public void disband_team() throws Exception { Long teamId = 1L; // 예시 ID DeleteTeamResponse output = DeleteTeamResponse.builder() .teamId(teamId) + .teamName("팀 이름") + .numOfMember(9) + .duration(30L) + .numOfMission(90L) + .levelOfFire(3) .build(); given(disbandTeamUserCase.disbandTeam(any(), any())).willReturn(output); @@ -196,7 +280,12 @@ public void disband_team() throws Exception { responseFields( fieldWithPath("isSuccess").description("true"), fieldWithPath("message").description("[소모임장 권한] 소모임을 강제 종료했습니다."), - fieldWithPath("data.teamId").description("강제종료한 소모임 id") + fieldWithPath("data.teamId").description("강제종료한 소모임 id"), + fieldWithPath("data.teamName").description("소모임 이름"), + fieldWithPath("data.numOfMember").description("모임원 명 수"), + fieldWithPath("data.duration").description("소모임과 함께한 시간"), + fieldWithPath("data.levelOfFire").description("소모임 불꽃 레벨"), + fieldWithPath("data.numOfMission").description("미션 총 개수") ) ) ); @@ -207,6 +296,11 @@ public void withdraw_team() throws Exception { Long teamId = 1L; // 예시 ID DeleteTeamResponse output = DeleteTeamResponse.builder() .teamId(teamId) + .teamName("팀 이름") + .numOfMember(9) + .duration(30L) + .numOfMission(90L) + .levelOfFire(3) .build(); given(withdrawTeamUserCase.withdrawTeam(any(), any())).willReturn(output); @@ -232,7 +326,12 @@ public void withdraw_team() throws Exception { responseFields( fieldWithPath("isSuccess").description("true"), fieldWithPath("message").description("[소모임원 권한] 소모임을 탈퇴하였습니다"), - fieldWithPath("data.teamId").description("탈퇴한 소모임 id") + fieldWithPath("data.teamId").description("탈퇴한 소모임 id"), + fieldWithPath("data.teamName").description("소모임 이름"), + fieldWithPath("data.numOfMember").description("모임원 명 수"), + fieldWithPath("data.duration").description("소모임과 함께한 시간"), + fieldWithPath("data.levelOfFire").description("소모임 불꽃 레벨"), + fieldWithPath("data.numOfMission").description("미션 총 개수") ) ) ); @@ -276,6 +375,7 @@ public void signIn_team() throws Exception { } + @Test public void update_team() throws Exception {