diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/MeetingCreationService.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/MeetingCreationService.java index 2cefa23a..3c8b0f9f 100644 --- a/src/main/java/com/jolupbisang/demo/application/meeting/command/MeetingCreationService.java +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/MeetingCreationService.java @@ -2,7 +2,13 @@ import com.jolupbisang.demo.application.meeting.command.dto.MeetingCreationReq; import com.jolupbisang.demo.application.meeting.command.dto.MeetingCreationRes; -import com.jolupbisang.demo.domain.meeting.model.*; +import com.jolupbisang.demo.domain.meeting.model.ActualProgressTime; +import com.jolupbisang.demo.domain.meeting.model.AgendaDetail; +import com.jolupbisang.demo.domain.meeting.model.Meeting; +import com.jolupbisang.demo.domain.meeting.model.MeetingRole; +import com.jolupbisang.demo.domain.meeting.model.ParticipantDetail; +import com.jolupbisang.demo.domain.meeting.model.RestTime; +import com.jolupbisang.demo.domain.meeting.model.ScheduledTime; import com.jolupbisang.demo.domain.user.User; import com.jolupbisang.demo.global.exception.NotFoundException; import com.jolupbisang.demo.infrastructure.meeting.MeetingRepository; @@ -49,7 +55,8 @@ private Meeting createMeeting(User host, List invitedUsers, MeetingCreatio actualProgressTime, restTime, participantDetails, - agendaDetails + agendaDetails, + req.teams() ); } diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/TeamTagAdditionService.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/TeamTagAdditionService.java new file mode 100644 index 00000000..4790f984 --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/TeamTagAdditionService.java @@ -0,0 +1,38 @@ +package com.jolupbisang.demo.application.meeting.command; + +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagAdditionReq; +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagAdditionRes; +import com.jolupbisang.demo.domain.meeting.model.Meeting; +import com.jolupbisang.demo.domain.team.model.Team; +import com.jolupbisang.demo.global.exception.NotFoundException; +import com.jolupbisang.demo.infrastructure.meeting.MeetingRepository; +import com.jolupbisang.demo.infrastructure.team.TeamRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class TeamTagAdditionService { + + private final MeetingRepository meetingRepository; + private final TeamRepository teamRepository; + + @Transactional + public TeamTagAdditionRes addTeamTag(Long meetingId, Long accessUserId, TeamTagAdditionReq request) { + Meeting meeting = meetingRepository.findByIdWithTeamTagsAndParticipants(meetingId) + .orElseThrow(() -> new NotFoundException("meetingId: %d", meetingId)); + + meeting.validateHostAuthority(accessUserId); + + Team team = teamRepository.findById(request.teamId()) + .orElseThrow(() -> new NotFoundException("teamId: %d", request.teamId())); + + team.validateViewAuthority(accessUserId); + + meeting.addTeamTag(request.teamId()); + + return new TeamTagAdditionRes(meeting.getId()); + } +} + diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/TeamTagRemovalService.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/TeamTagRemovalService.java new file mode 100644 index 00000000..cd227468 --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/TeamTagRemovalService.java @@ -0,0 +1,38 @@ +package com.jolupbisang.demo.application.meeting.command; + +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagRemovalReq; +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagRemovalRes; +import com.jolupbisang.demo.domain.meeting.model.Meeting; +import com.jolupbisang.demo.domain.team.model.Team; +import com.jolupbisang.demo.global.exception.NotFoundException; +import com.jolupbisang.demo.infrastructure.meeting.MeetingRepository; +import com.jolupbisang.demo.infrastructure.team.TeamRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class TeamTagRemovalService { + + private final MeetingRepository meetingRepository; + private final TeamRepository teamRepository; + + @Transactional + public TeamTagRemovalRes removeTeamTag(Long meetingId, Long accessUserId, TeamTagRemovalReq request) { + Meeting meeting = meetingRepository.findByIdWithTeamTagsAndParticipants(meetingId) + .orElseThrow(() -> new NotFoundException("meetingId: %d", meetingId)); + + meeting.validateHostAuthority(accessUserId); + + Team team = teamRepository.findById(request.teamId()) + .orElseThrow(() -> new NotFoundException("teamId: %d", request.teamId())); + + team.validateViewAuthority(accessUserId); + + meeting.removeTeamTag(request.teamId()); + + return new TeamTagRemovalRes(meeting.getId()); + } +} + diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/MeetingCreationReq.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/MeetingCreationReq.java index 86ed86af..2f72b4de 100644 --- a/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/MeetingCreationReq.java +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/MeetingCreationReq.java @@ -43,6 +43,10 @@ public record MeetingCreationReq( @Schema(description = "회의 안건 목록") @NotNull(message = "회의 안건 목록은 필수입니다.") - List<@NotBlank(message = "회의 안건은 1글자 이상이어야 합니다.") String> agendas + List<@NotBlank(message = "회의 안건은 1글자 이상이어야 합니다.") String> agendas, + + @Schema(description = "팀 ID 목록", example = "[1, 2, 3]") + @NotNull(message = "팀 목록은 필수입니다.") + List teams ) { } diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagAdditionReq.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagAdditionReq.java new file mode 100644 index 00000000..755f1376 --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagAdditionReq.java @@ -0,0 +1,10 @@ +package com.jolupbisang.demo.application.meeting.command.dto; + +import jakarta.validation.constraints.NotNull; + +public record TeamTagAdditionReq( + @NotNull(message = "팀 ID는 필수입니다.") + Long teamId +) { +} + diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagAdditionRes.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagAdditionRes.java new file mode 100644 index 00000000..8a1f21ad --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagAdditionRes.java @@ -0,0 +1,7 @@ +package com.jolupbisang.demo.application.meeting.command.dto; + +public record TeamTagAdditionRes( + long meetingId +) { +} + diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagRemovalReq.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagRemovalReq.java new file mode 100644 index 00000000..b8a3ab4e --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagRemovalReq.java @@ -0,0 +1,10 @@ +package com.jolupbisang.demo.application.meeting.command.dto; + +import jakarta.validation.constraints.NotNull; + +public record TeamTagRemovalReq( + @NotNull(message = "팀 ID는 필수입니다.") + Long teamId +) { +} + diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagRemovalRes.java b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagRemovalRes.java new file mode 100644 index 00000000..b782fa56 --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/application/meeting/command/dto/TeamTagRemovalRes.java @@ -0,0 +1,7 @@ +package com.jolupbisang.demo.application.meeting.command.dto; + +public record TeamTagRemovalRes( + long meetingId +) { +} + diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/query/MeetingDetailQueryService.java b/src/main/java/com/jolupbisang/demo/application/meeting/query/MeetingDetailQueryService.java index 3ca85ecd..8aa7101b 100644 --- a/src/main/java/com/jolupbisang/demo/application/meeting/query/MeetingDetailQueryService.java +++ b/src/main/java/com/jolupbisang/demo/application/meeting/query/MeetingDetailQueryService.java @@ -5,9 +5,11 @@ import com.jolupbisang.demo.application.meeting.query.dto.ParticipantInfoRes; import com.jolupbisang.demo.domain.meeting.model.Meeting; import com.jolupbisang.demo.domain.meeting.model.Participant; +import com.jolupbisang.demo.domain.team.model.Team; import com.jolupbisang.demo.domain.user.User; import com.jolupbisang.demo.global.exception.NotFoundException; import com.jolupbisang.demo.infrastructure.meeting.MeetingRepository; +import com.jolupbisang.demo.infrastructure.team.TeamRepository; import com.jolupbisang.demo.infrastructure.user.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,6 +25,7 @@ public class MeetingDetailQueryService { private final MeetingRepository meetingRepository; private final UserRepository userRepository; + private final TeamRepository teamRepository; private static final String NOT_FOUND_USER_EMAIL = "알 수 없음"; @@ -35,9 +38,10 @@ public MeetingDetailRes getMeetingDetail(long meetingId, long accessUserId) { List participantInfos = getParticipantInfoRes(meeting); List agendaInfoRes = getAgendaInfoRes(meeting); + List teamNames = getTeamNames(meeting); boolean isHost = meeting.isHost(accessUserId); - return MeetingDetailRes.from(meeting, participantInfos, agendaInfoRes, isHost); + return MeetingDetailRes.from(meeting, participantInfos, agendaInfoRes, teamNames, isHost); } private List getParticipantInfoRes(Meeting meeting) { @@ -69,4 +73,19 @@ private List getAgendaInfoRes(Meeting meeting) { .map(agenda -> new AgendaInfoRes(agenda.getId(), agenda.getContent(), agenda.getIsCompleted())) .collect(Collectors.toList()); } + + private List getTeamNames(Meeting meeting) { + List teamIds = meeting.getTeamTags().stream() + .map(teamTag -> teamTag.getTeamId()) + .toList(); + + if (teamIds.isEmpty()) { + return List.of(); + } + + List teams = teamRepository.findAllById(teamIds); + return teams.stream() + .map(team -> team.getTeamName().getName()) + .toList(); + } } diff --git a/src/main/java/com/jolupbisang/demo/application/meeting/query/dto/MeetingDetailRes.java b/src/main/java/com/jolupbisang/demo/application/meeting/query/dto/MeetingDetailRes.java index 8eb8ad09..63ef3b20 100644 --- a/src/main/java/com/jolupbisang/demo/application/meeting/query/dto/MeetingDetailRes.java +++ b/src/main/java/com/jolupbisang/demo/application/meeting/query/dto/MeetingDetailRes.java @@ -18,10 +18,11 @@ public record MeetingDetailRes( String meetingStatus, List participants, List agendas, + List teamNames, boolean isHost ) { - public static MeetingDetailRes from(Meeting meeting, List participantInfos, List agendas, boolean isHost) { + public static MeetingDetailRes from(Meeting meeting, List participantInfos, List agendas, List teamNames, boolean isHost) { return new MeetingDetailRes( meeting.getId(), meeting.getTitle(), @@ -35,6 +36,7 @@ public static MeetingDetailRes from(Meeting meeting, List pa meeting.getMeetingStatus().name(), participantInfos, agendas, + teamNames, isHost ); } diff --git a/src/main/java/com/jolupbisang/demo/application/team/query/TeamListQueryService.java b/src/main/java/com/jolupbisang/demo/application/team/query/TeamListQueryService.java index 9fb0617b..6e350118 100644 --- a/src/main/java/com/jolupbisang/demo/application/team/query/TeamListQueryService.java +++ b/src/main/java/com/jolupbisang/demo/application/team/query/TeamListQueryService.java @@ -1,12 +1,15 @@ package com.jolupbisang.demo.application.team.query; import com.jolupbisang.demo.application.team.query.dto.TeamListRes; +import com.jolupbisang.demo.domain.meeting.model.Meeting; import com.jolupbisang.demo.domain.team.model.Team; +import com.jolupbisang.demo.infrastructure.meeting.MeetingRepository; import com.jolupbisang.demo.infrastructure.team.TeamRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; @Service @@ -14,13 +17,19 @@ public class TeamListQueryService { private final TeamRepository teamRepository; + private final MeetingRepository meetingRepository; @Transactional(readOnly = true) public List getTeamsByUserId(Long userId) { List teams = teamRepository.findByMembersUserId(userId); + LocalDateTime now = LocalDateTime.now(); return teams.stream() - .map(TeamListRes::fromEntity) + .map(team -> { + Meeting closestMeeting = meetingRepository.findClosestMeetingByTeamId(team.getId(), now) + .orElse(null); + return TeamListRes.fromEntity(team, closestMeeting); + }) .toList(); } } diff --git a/src/main/java/com/jolupbisang/demo/application/team/query/dto/TeamListRes.java b/src/main/java/com/jolupbisang/demo/application/team/query/dto/TeamListRes.java index e730d0a8..30de22de 100644 --- a/src/main/java/com/jolupbisang/demo/application/team/query/dto/TeamListRes.java +++ b/src/main/java/com/jolupbisang/demo/application/team/query/dto/TeamListRes.java @@ -1,15 +1,33 @@ package com.jolupbisang.demo.application.team.query.dto; +import com.jolupbisang.demo.domain.meeting.model.Meeting; import com.jolupbisang.demo.domain.team.model.Team; +import java.time.LocalDateTime; + public record TeamListRes( Long teamId, - String teamName + String teamName, + String meetingName, + LocalDateTime scheduledStartTime, + LocalDateTime scheduledEndTime ) { - public static TeamListRes fromEntity(Team team) { + public static TeamListRes fromEntity(Team team, Meeting closestMeeting) { + if (closestMeeting != null) { + return new TeamListRes( + team.getId(), + team.getTeamName().getName(), + closestMeeting.getTitle(), + closestMeeting.getScheduledTime().getScheduledStartTime(), + closestMeeting.getScheduledTime().getScheduledEndTime() + ); + } return new TeamListRes( team.getId(), - team.getTeamName().getName() + team.getTeamName().getName(), + null, + null, + null ); } } diff --git a/src/main/java/com/jolupbisang/demo/domain/meeting/exception/MeetingDomainErrorCode.java b/src/main/java/com/jolupbisang/demo/domain/meeting/exception/MeetingDomainErrorCode.java index c39b57a1..a5136900 100644 --- a/src/main/java/com/jolupbisang/demo/domain/meeting/exception/MeetingDomainErrorCode.java +++ b/src/main/java/com/jolupbisang/demo/domain/meeting/exception/MeetingDomainErrorCode.java @@ -33,7 +33,8 @@ public enum MeetingDomainErrorCode implements ErrorCode { INVALID_TOTAL_PARTICIPATION_CHUNK("MD-0024", HttpStatus.BAD_REQUEST, "참여 청크는 0보다 크거나 같아야합니다."), NON_EXISTING_AGENDA("MD-0025", HttpStatus.NOT_FOUND, "없는 아젠다 입니다."), NOT_PARTICIPANT("MD-0026", HttpStatus.UNAUTHORIZED, "회의 참여자만 가능합니다."), - NEGATIVE_USER_ID("MD-0027", HttpStatus.BAD_REQUEST, "사용자 Id는 0보다 크거나 같아야합니다."); + NEGATIVE_USER_ID("MD-0027", HttpStatus.BAD_REQUEST, "사용자 id는 0보다 크거나 같아야합니다."), + NULL_TEAM("MD-0028", HttpStatus.BAD_REQUEST, "팀은 필수입니다."); private final String code; private final HttpStatus status; diff --git a/src/main/java/com/jolupbisang/demo/domain/meeting/model/Meeting.java b/src/main/java/com/jolupbisang/demo/domain/meeting/model/Meeting.java index e1607429..ea16f086 100644 --- a/src/main/java/com/jolupbisang/demo/domain/meeting/model/Meeting.java +++ b/src/main/java/com/jolupbisang/demo/domain/meeting/model/Meeting.java @@ -67,9 +67,12 @@ public class Meeting extends BaseTimeEntity { @OneToMany(mappedBy = "meeting", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private final List agendas = new ArrayList<>(); + @OneToMany(mappedBy = "meeting", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + private final List teamTags = new ArrayList<>(); + private static final long MAX_MEETING_HOST_COUNT = 1L; - public Meeting(String title, String location, ScheduledTime scheduledTime, ActualProgressTime actualProgressTime, RestTime restTime, List participantDetails, List agendaDetails) { + public Meeting(String title, String location, ScheduledTime scheduledTime, ActualProgressTime actualProgressTime, RestTime restTime, List participantDetails, List agendaDetails, List teamIds) { setTitle(title); setLocation(location); setScheduledTime(scheduledTime); @@ -77,6 +80,7 @@ public Meeting(String title, String location, ScheduledTime scheduledTime, Actua setRestTime(restTime); setParticipants(participantDetails); setAgendas(agendaDetails); + setTeamTags(teamIds); initiateStatus(); } @@ -169,6 +173,17 @@ public void addAgendas(List agendaDetails, long accessUserId) { } } + public void updateAgenda(long agendaId, long accessUserId, String content) { + validateHostAuthority(accessUserId); + + Agenda foundAgenda = agendas.stream() + .filter(a -> a.getId().equals(agendaId)) + .findFirst() + .orElseThrow(() -> new DomainException(MeetingDomainErrorCode.NON_EXISTING_AGENDA, "agendaId: %d", agendaId)); + + foundAgenda.updateContent(content); + } + public void changeAgendaStatus(long agendaId, long accessUserId, boolean isCompleted) { validateHostAuthority(accessUserId); @@ -197,6 +212,15 @@ public void validateViewAuthority(long accessUserId) { } } + public void validateHostAuthority(long accessUserId) { + boolean isHost = participants.stream() + .anyMatch(p -> p.getUserId().equals(accessUserId) && p.getRole() == MeetingRole.HOST); + + if (!isHost) { + throw new DomainException(MeetingDomainErrorCode.ONLY_FOR_HOST_AUTHORITY); + } + } + public boolean isHost(long userId) { return participants.stream() .anyMatch(p -> p.getUserId().equals(userId) && p.getRole() == MeetingRole.HOST); @@ -230,6 +254,22 @@ public void updateParticipationRates(Map participationRates, Map teamTag.getTeamId().equals(teamId)); + } + + public boolean hasTeamTag(Long teamId) { + return teamTags.stream() + .anyMatch(teamTag -> teamTag.getTeamId().equals(teamId)); + } + private void setTitle(String title) { if (title == null || title.isBlank()) { throw new DomainException(MeetingDomainErrorCode.EMPTY_TITLE); @@ -277,6 +317,12 @@ private void setAgendas(List agendaDetails) { .forEach(agendas::add); } + private void setTeamTags(List teamIds) { + if (teamIds != null && !teamIds.isEmpty()) { + teamIds.forEach(teamId -> teamTags.add(new TeamTag(this, teamId))); + } + } + private void initiateStatus() { meetingStatus = MeetingStatus.WAITING; } @@ -290,24 +336,4 @@ private void validateHostCount() { throw new TooManyHostException(MeetingDomainErrorCode.TOO_MANY_HOST, "hostCount: %d", hostCount); } } - - private void validateHostAuthority(long accessUserId) { - boolean isHost = participants.stream() - .anyMatch(p -> p.getUserId().equals(accessUserId) && p.getRole() == MeetingRole.HOST); - - if (!isHost) { - throw new DomainException(MeetingDomainErrorCode.ONLY_FOR_HOST_AUTHORITY); - } - } - - public void updateAgenda(long agendaId, long accessUserId, String content) { - validateHostAuthority(accessUserId); - - Agenda foundAgenda = agendas.stream() - .filter(a -> a.getId().equals(agendaId)) - .findFirst() - .orElseThrow(() -> new DomainException(MeetingDomainErrorCode.NON_EXISTING_AGENDA, "agendaId: %d", agendaId)); - - foundAgenda.updateContent(content); - } } diff --git a/src/main/java/com/jolupbisang/demo/domain/meeting/model/TeamTag.java b/src/main/java/com/jolupbisang/demo/domain/meeting/model/TeamTag.java new file mode 100644 index 00000000..aa03f744 --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/domain/meeting/model/TeamTag.java @@ -0,0 +1,54 @@ +package com.jolupbisang.demo.domain.meeting.model; + +import com.jolupbisang.demo.domain.meeting.exception.MeetingDomainErrorCode; +import com.jolupbisang.demo.global.exception.DomainException; +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.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "team_tag") +public class TeamTag { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meeting_id", nullable = false) + private Meeting meeting; + + @Column(name = "team_id", nullable = false) + private Long teamId; + + public TeamTag(Meeting meeting, Long teamId) { + setMeeting(meeting); + setTeamId(teamId); + } + + private void setMeeting(Meeting meeting) { + if (meeting == null) { + throw new DomainException(MeetingDomainErrorCode.NULL_TEAM); + } + this.meeting = meeting; + } + + private void setTeamId(Long teamId) { + if (teamId == null || teamId <= 0) { + throw new DomainException(MeetingDomainErrorCode.NULL_TEAM); + } + this.teamId = teamId; + } +} + diff --git a/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryCustom.java b/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryCustom.java index 755af1c6..6fb7714a 100644 --- a/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryCustom.java +++ b/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryCustom.java @@ -17,5 +17,9 @@ public interface MeetingRepositoryCustom { Optional findByIdWithAllDetail(long meetingId); + Optional findByIdWithTeamTagsAndParticipants(long meetingId); + List findByScheduledTimeAndParticipantAndStatuses(LocalDateTime startTime, LocalDateTime endTime, long userId, Set statuses); + + Optional findClosestMeetingByTeamId(Long teamId, LocalDateTime now); } diff --git a/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryImpl.java b/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryImpl.java index aa3c676c..0fb8950e 100644 --- a/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryImpl.java +++ b/src/main/java/com/jolupbisang/demo/infrastructure/meeting/MeetingRepositoryImpl.java @@ -16,6 +16,7 @@ import static com.jolupbisang.demo.domain.meeting.model.QAgenda.agenda; import static com.jolupbisang.demo.domain.meeting.model.QMeeting.meeting; import static com.jolupbisang.demo.domain.meeting.model.QParticipant.participant; +import static com.jolupbisang.demo.domain.meeting.model.QTeamTag.teamTag; @RequiredArgsConstructor @@ -73,6 +74,29 @@ public Optional findByIdWithAllDetail(long meetingId) { .leftJoin(meeting.agendas, agenda).fetchJoin() .where(meeting.id.eq(meetingId)) .fetchOne(); + + queryFactory.selectFrom(meeting) + .leftJoin(meeting.teamTags, teamTag).fetchJoin() + .where(meeting.id.eq(meetingId)) + .fetchOne(); + } + + return Optional.ofNullable(resultMeeting); + } + + @Override + public Optional findByIdWithTeamTagsAndParticipants(long meetingId) { + Meeting resultMeeting = queryFactory + .selectFrom(meeting) + .leftJoin(meeting.participants, participant).fetchJoin() + .where(meeting.id.eq(meetingId)) + .fetchOne(); + + if (resultMeeting != null) { + queryFactory.selectFrom(meeting) + .leftJoin(meeting.teamTags, teamTag).fetchJoin() + .where(meeting.id.eq(meetingId)) + .fetchOne(); } return Optional.ofNullable(resultMeeting); @@ -89,4 +113,34 @@ public List findByScheduledTimeAndParticipantAndStatuses(LocalDateTime .and(meeting.meetingStatus.in(statuses))) .fetch(); } + + @Override + public Optional findClosestMeetingByTeamId(Long teamId, LocalDateTime now) { + // 1. 미래 회의 중 가장 가까운 회의 조회 + Meeting futureMeeting = queryFactory + .selectFrom(meeting) + .innerJoin(meeting.teamTags, teamTag) + .where(teamTag.teamId.eq(teamId) + .and(meeting.scheduledTime.scheduledStartTime.gt(now))) + .orderBy(meeting.scheduledTime.scheduledStartTime.asc()) + .limit(1) + .fetchOne(); + + if (futureMeeting != null) { + return Optional.of(futureMeeting); + } + + // 2. 지난 회의 중 가장 최근 회의 조회 + Meeting pastMeeting = queryFactory + .selectFrom(meeting) + .innerJoin(meeting.teamTags, teamTag) + .where(teamTag.teamId.eq(teamId) + .and(meeting.scheduledTime.scheduledStartTime.loe(now))) + .orderBy(meeting.scheduledTime.scheduledStartTime.desc()) + .limit(1) + .fetchOne(); + + return Optional.ofNullable(pastMeeting); + } } + diff --git a/src/main/java/com/jolupbisang/demo/presentation/meeting/TeamTagAdditionController.java b/src/main/java/com/jolupbisang/demo/presentation/meeting/TeamTagAdditionController.java new file mode 100644 index 00000000..4c8ead8b --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/presentation/meeting/TeamTagAdditionController.java @@ -0,0 +1,34 @@ +package com.jolupbisang.demo.presentation.meeting; + +import com.jolupbisang.demo.application.meeting.command.TeamTagAdditionService; +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagAdditionReq; +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagAdditionRes; +import com.jolupbisang.demo.infrastructure.auth.security.CustomUserDetails; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class TeamTagAdditionController { + + private final TeamTagAdditionService teamTagAdditionService; + + @PostMapping("/api/v1/meetings/{meetingId}/team-tags") + public ResponseEntity addTeamTag( + @PathVariable Long meetingId, + @RequestBody @Valid TeamTagAdditionReq request, + @AuthenticationPrincipal CustomUserDetails userDetails) { + + return ResponseEntity + .status(HttpStatus.OK) + .body(teamTagAdditionService.addTeamTag(meetingId, userDetails.getUserId(), request)); + } +} + diff --git a/src/main/java/com/jolupbisang/demo/presentation/meeting/TeamTagRemovalController.java b/src/main/java/com/jolupbisang/demo/presentation/meeting/TeamTagRemovalController.java new file mode 100644 index 00000000..ebb26fef --- /dev/null +++ b/src/main/java/com/jolupbisang/demo/presentation/meeting/TeamTagRemovalController.java @@ -0,0 +1,34 @@ +package com.jolupbisang.demo.presentation.meeting; + +import com.jolupbisang.demo.application.meeting.command.TeamTagRemovalService; +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagRemovalReq; +import com.jolupbisang.demo.application.meeting.command.dto.TeamTagRemovalRes; +import com.jolupbisang.demo.infrastructure.auth.security.CustomUserDetails; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class TeamTagRemovalController { + + private final TeamTagRemovalService teamTagRemovalService; + + @DeleteMapping("/api/v1/meetings/{meetingId}/team-tags") + public ResponseEntity removeTeamTag( + @PathVariable Long meetingId, + @RequestBody @Valid TeamTagRemovalReq request, + @AuthenticationPrincipal CustomUserDetails userDetails) { + + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .body(teamTagRemovalService.removeTeamTag(meetingId, userDetails.getUserId(), request)); + } +} +