From 8e81df583eff83f497d6a8515afa5e3f45232ac2 Mon Sep 17 00:00:00 2001 From: FacerAin Date: Tue, 20 Feb 2024 15:22:00 +0900 Subject: [PATCH 01/23] [#-] feat: Update docker compose for db production --- docker-compose.yml | 47 ++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 84345a5..720573d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,25 +1,40 @@ version: '3.7' services: - db: - image: mysql:8-oracle - restart: always - environment: - MYSQL_DATABASE: '{MYSQL_DATABASE}' - MYSQL_USER: '${MYSQL_USER}' - MYSQL_PASSWORD: '${MYSQL_PASSWORD}' - MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}' + database: + container_name: mysql_db + image: mysql:8 + restart: unless-stopped + env_file: + - .env ports: - - '3306:3306' + - "3306:3306" volumes: - - db_data:/var/lib/mysql + - ./mysql/conf.d:/etc/mysql/conf.d + command: + - "mysqld" + - "--character-set-server=utf8mb4" + - "--collation-server=utf8mb4_unicode_ci" + networks: + - backend_network - app: - image: syw5141/dnd-10th-2-backend:latest + application: + container_name: backend + restart: on-failure + build: + context: ./ + dockerfile: Dockerfile ports: - "8080:8080" + env_file: + - .env + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql_db:3306/${MYSQL_DATABASE}?useSSL=false&allowPublicKeyRetrieval=true + SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME} + SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD} depends_on: - - db + - database + networks: + - backend_network -# 볼륨 정의 (기본 설정 사용) -volumes: - db_data: \ No newline at end of file +networks: + backend_network: \ No newline at end of file From bcafe14b717e336c605f84b711dbe96c732cfb06 Mon Sep 17 00:00:00 2001 From: FacerAin Date: Tue, 20 Feb 2024 15:24:23 +0900 Subject: [PATCH 02/23] [#-] feat: Update docker compose for db production --- docker-compose.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 720573d..4422bcd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,9 +20,7 @@ services: application: container_name: backend restart: on-failure - build: - context: ./ - dockerfile: Dockerfile + image: syw5141/dnd-10th-2-backend:latest ports: - "8080:8080" env_file: From ec1a0d128ebd2823d16c4c49a74d94ac0f7003ec Mon Sep 17 00:00:00 2001 From: FacerAin Date: Tue, 20 Feb 2024 15:31:47 +0900 Subject: [PATCH 03/23] [#-] feat: Update docker compose for db production --- docker-compose.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4422bcd..c9eea5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,12 @@ services: application: container_name: backend - restart: on-failure + deploy: + restart_policy: + condition: on-failure + max_attempts: 5 + delay: 3s + image: syw5141/dnd-10th-2-backend:latest ports: - "8080:8080" From c5bc1b2b9fdca39e62de325f21ed1f196f760a7b Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 16:55:41 +0900 Subject: [PATCH 04/23] [#59] feat: Implement leaving meeting feature --- .../meeting/application/MeetingService.java | 42 +++++++++++-------- .../meeting/controller/MeetingController.java | 7 ++++ .../dnd/timeet/meeting/domain/Meeting.java | 29 ++++++++++++- .../participant/domain/Participant.java | 21 +++++----- 4 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index 5dac305..2201a3e 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -12,7 +12,6 @@ import org.dnd.timeet.agenda.domain.AgendaStatus; import org.dnd.timeet.agenda.domain.AgendaType; import org.dnd.timeet.agenda.dto.AgendaReportInfoResponse; -import org.dnd.timeet.common.exception.BadRequestError; import org.dnd.timeet.common.exception.ForbiddenError; import org.dnd.timeet.common.exception.NotFoundError; import org.dnd.timeet.common.exception.NotFoundError.ErrorCode; @@ -22,6 +21,7 @@ import org.dnd.timeet.meeting.dto.MeetingRemainingTimeResponse; import org.dnd.timeet.meeting.dto.MeetingReportInfoResponse; import org.dnd.timeet.member.domain.Member; +import org.dnd.timeet.member.domain.MemberRepository; import org.dnd.timeet.participant.domain.Participant; import org.dnd.timeet.participant.domain.ParticipantRepository; import org.springframework.stereotype.Service; @@ -36,6 +36,7 @@ public class MeetingService { private final ParticipantRepository participantRepository; private final AgendaRepository agendaRepository; private final MeetingScheduler meetingScheduler; + private final MemberRepository memberRepository; public Meeting createMeeting(MeetingCreateRequest createDto, Member member) { @@ -79,23 +80,7 @@ public Meeting addParticipantToMeeting(Long meetingId, Member member) { .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); - // 멤버가 이미 회의에 참가하고 있는지 확인 - boolean alreadyParticipating = meeting.getParticipants().stream() - .anyMatch(participant -> participant.getMember().equals(member)); - - if (alreadyParticipating) { - // 에러 메세지 발생 - throw new BadRequestError(BadRequestError.ErrorCode.DUPLICATE_RESOURCE, - Collections.singletonMap("Member", "Member already participating in the meeting")); - } - - // Participant 인스턴스 생성 및 저장 - Participant participant = new Participant(meeting, member); - participantRepository.save(participant); - - // 양방향 연관관계 설정 - meeting.getParticipants().add(participant); - member.getParticipations().add(participant); + participantRepository.save(meeting.addParticipant(member)); return meeting; } @@ -166,4 +151,25 @@ public MeetingRemainingTimeResponse getRemainingTime(Long meetingId) { return MeetingRemainingTimeResponse.from(meeting); } + public void leaveMeeting(Long meetingId, Long memberId) { + Meeting meeting = meetingRepository.findById(meetingId) + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MeetingId", "Meeting not found"))); + memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MemberId", "Member not found"))); + + Participant participant = participantRepository.findByMeetingIdAndMemberId(meetingId, memberId) + .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("ParticipantId", "Participant not found"))); + + // 방장이 나가는 경우 새로운 방장 지정 + if (meeting.getHostMember().getId().equals(memberId)) { + meeting.assignNewHostRandomly(); + meetingRepository.save(meeting); + } + + participant.removeParticipant(); + participantRepository.save(participant); + } } diff --git a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java index d899a70..59fc6eb 100644 --- a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java +++ b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java @@ -130,4 +130,11 @@ public ResponseEntity> getMeetingMembers( return ResponseEntity.ok(ApiUtils.success(memberInfoListResponse)); } + @DeleteMapping("/{meeting-id}/leave") + @Operation(summary = "회의실 나가기", description = "지정된 id에 해당하는 회의에서 나간다.") + public ResponseEntity leaveMeeting(@PathVariable("meeting-id") Long meetingId, @ReqUser Member member) { + meetingService.leaveMeeting(meetingId, member.getId()); + + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java b/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java index b28833e..15996bb 100644 --- a/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java +++ b/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.Random; import java.util.Set; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -140,8 +139,14 @@ public void assignNewHostRandomly() { List participantsList = this.participants.stream() .map(Participant::getMember) - .collect(Collectors.toList()); + .filter(member -> !member.equals(this.hostMember)) // 현재 방장 제외 + .toList(); + // 회의에 방장만 존재할 경우 + if (participantsList.isEmpty()) { + this.hostMember = null; + return; + } // 랜덤 객체를 사용하여 참가자 목록에서 랜덤하게 하나를 선택 Random random = new Random(); int index = random.nextInt(participantsList.size()); @@ -191,5 +196,25 @@ public Duration calculateRemainingTime() { } } + public boolean isMemberParticipating(Member member) { + return this.participants.stream() + .anyMatch(participant -> participant.getMember().equals(member)); + } + + public Participant addParticipant(Member member) { + // 이미 참가중인 회원이라면 예외 발생 + if (this.isMemberParticipating(member)) { + throw new BadRequestError(BadRequestError.ErrorCode.DUPLICATE_RESOURCE, + Collections.singletonMap("Member", "Member already participating in the meeting")); + } + + // 회의에 아무도 없다면 방장으로 지정 + if (this.participants.isEmpty()) { + this.assignHostMember(member); + } + + return new Participant(this, member); + } + } diff --git a/src/main/java/org/dnd/timeet/participant/domain/Participant.java b/src/main/java/org/dnd/timeet/participant/domain/Participant.java index 4783e79..2209bc2 100644 --- a/src/main/java/org/dnd/timeet/participant/domain/Participant.java +++ b/src/main/java/org/dnd/timeet/participant/domain/Participant.java @@ -4,25 +4,15 @@ import jakarta.persistence.AttributeOverride; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.dnd.timeet.common.domain.AuditableEntity; -import org.dnd.timeet.common.exception.BadRequestError; import org.dnd.timeet.meeting.domain.Meeting; import org.dnd.timeet.member.domain.Member; import org.hibernate.annotations.Where; @@ -52,6 +42,17 @@ public Participant(Meeting meeting, Member member) { member.getParticipations().add(this); } + public void removeParticipant() { + if (this.meeting != null) { + this.meeting.getParticipants().remove(this); + this.meeting = null; + } + if (this.member != null) { + this.member.getParticipations().remove(this); + this.member = null; + } + this.delete(); + } } From ae8b70761749dc08b85327bd140960fbf3a8ee31 Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 17:16:34 +0900 Subject: [PATCH 05/23] [#59] refactor: Refactor member retrieving feature to allow even without members --- .../org/dnd/timeet/meeting/application/MeetingService.java | 6 ++++++ .../org/dnd/timeet/meeting/domain/MeetingRepository.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index 2201a3e..f352197 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -137,6 +137,12 @@ public List getMeetingMembers(Long meetingId) { .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); + // 회의에 참가자가 없는 경우 빈 리스트 반환 + if (meeting.getParticipants().isEmpty()) { + return Collections.emptyList(); + } + + // 참가자 목록을 Member 객체의 리스트로 변환하여 반환 return meeting.getParticipants().stream() .map(Participant::getMember) .collect(Collectors.toList()); diff --git a/src/main/java/org/dnd/timeet/meeting/domain/MeetingRepository.java b/src/main/java/org/dnd/timeet/meeting/domain/MeetingRepository.java index cd9512f..d523685 100644 --- a/src/main/java/org/dnd/timeet/meeting/domain/MeetingRepository.java +++ b/src/main/java/org/dnd/timeet/meeting/domain/MeetingRepository.java @@ -8,7 +8,7 @@ public interface MeetingRepository extends JpaRepository { - @Query("select m from Meeting m join fetch m.participants p join fetch p.member where m.id = :meetingId") + @Query("select m from Meeting m left join fetch m.participants p left join fetch p.member where m.id = :meetingId") Optional findByIdWithParticipantsAndMembers(@Param("meetingId") Long meetingId); @Query("SELECT m FROM Meeting m WHERE m.status = 'INPROGRESS'") From 9827e35bb9732f724fc3d454e3ce73d19a514e5c Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 17:25:21 +0900 Subject: [PATCH 06/23] [#59] refactor: Refactor meeting retrieving feature to allow even without host member --- .../java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java index b692725..60ed549 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java @@ -66,7 +66,7 @@ public static MeetingInfoResponse from(Meeting meeting) { // 매개변수로부 .title(meeting.getTitle()) .description(meeting.getDescription()) .meetingStatus(meeting.getStatus().name()) - .hostMemberId(meeting.getHostMember().getId()) + .hostMemberId(meeting.getHostMember() == null ? null : meeting.getHostMember().getId()) .startTime(meeting.getStartTime().toString()) .totalEstimatedDuration(meeting.getTotalEstimatedDuration()) .remainingTime(meeting.calculateRemainingTime()) From f7f983852e64600dea863f015bc8b12c29e4a9a8 Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 18:58:13 +0900 Subject: [PATCH 07/23] [#60] feat: Implement patching agenda feature --- .../agenda/application/AgendaService.java | 24 +++++++++++++ .../agenda/controller/AgendaController.java | 14 ++++++++ .../org/dnd/timeet/agenda/domain/Agenda.java | 6 ++++ .../timeet/agenda/dto/AgendaPatchRequest.java | 25 +++++++++++++ .../agenda/dto/AgendaPatchResponse.java | 35 +++++++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java create mode 100644 src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java diff --git a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java index b172b4b..b5b4bbc 100644 --- a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java +++ b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java @@ -13,6 +13,8 @@ import org.dnd.timeet.agenda.dto.AgendaActionResponse; import org.dnd.timeet.agenda.dto.AgendaCreateRequest; import org.dnd.timeet.agenda.dto.AgendaInfoResponse; +import org.dnd.timeet.agenda.dto.AgendaPatchRequest; +import org.dnd.timeet.agenda.dto.AgendaPatchResponse; import org.dnd.timeet.common.exception.BadRequestError; import org.dnd.timeet.common.exception.NotFoundError; import org.dnd.timeet.common.exception.NotFoundError.ErrorCode; @@ -146,4 +148,26 @@ public AgendaInfoResponse findAgendas(Long meetingId) { return new AgendaInfoResponse(meeting, agendaList); } + + public AgendaPatchResponse patchAgenda(Long meetingId, Long agendaId, AgendaPatchRequest patchRequest) { + // 회의 존재 여부만 확인 + boolean meetingExists = meetingRepository.existsById(meetingId); + if (!meetingExists) { + throw new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MeetingId", "Meeting not found")); + } + + Agenda agenda = agendaRepository.findByIdAndMeetingId(agendaId, meetingId) + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("AgendaId", "Agenda not found"))); + if (agenda.getStatus() != AgendaStatus.PENDING) { + throw new BadRequestError(BadRequestError.ErrorCode.WRONG_REQUEST_TRANSMISSION, + Collections.singletonMap("AgendaStatus", "Agenda is not PENDING status")); + } + + agenda.update(patchRequest.getTitle(), + DurationUtils.convertLocalTimeToDuration(patchRequest.getAllocatedDuration()), patchRequest.getOrderNum()); + + return new AgendaPatchResponse(agenda); + } } diff --git a/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java b/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java index 13b8769..86057b1 100644 --- a/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java +++ b/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java @@ -9,6 +9,8 @@ import org.dnd.timeet.agenda.dto.AgendaActionResponse; import org.dnd.timeet.agenda.dto.AgendaCreateRequest; import org.dnd.timeet.agenda.dto.AgendaInfoResponse; +import org.dnd.timeet.agenda.dto.AgendaPatchRequest; +import org.dnd.timeet.agenda.dto.AgendaPatchResponse; import org.dnd.timeet.common.security.CustomUserDetails; import org.dnd.timeet.common.utils.ApiUtils; import org.dnd.timeet.common.utils.ApiUtils.ApiResult; @@ -19,6 +21,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -81,4 +84,15 @@ public ResponseEntity deleteAgenda( return ResponseEntity.noContent().build(); } + + @PatchMapping("/{meeting-id}/agendas/{agenda-id}") + @Operation(summary = "안건 수정", description = "지정된 ID에 해당하는 안건을 수정한다.") + public ResponseEntity> deleteAgenda( + @PathVariable("meeting-id") Long meetingId, + @PathVariable("agenda-id") Long agendaId, + @RequestBody AgendaPatchRequest patchRequest) { + AgendaPatchResponse response = agendaService.patchAgenda(meetingId, agendaId, patchRequest); + + return ResponseEntity.ok(ApiUtils.success(response)); + } } diff --git a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java index 2c50b4f..9fcfed7 100644 --- a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java +++ b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java @@ -158,5 +158,11 @@ public void cancel() { this.delete(); } + public void update(String title, Duration allocatedDuration, Integer orderNum) { + this.title = title; + this.allocatedDuration = allocatedDuration; + this.orderNum = orderNum; + } + } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java new file mode 100644 index 0000000..4a68335 --- /dev/null +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java @@ -0,0 +1,25 @@ +package org.dnd.timeet.agenda.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalTime; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; + +@Schema(description = "안건 수정 요청") +@Getter +@Setter +@NoArgsConstructor +public class AgendaPatchRequest { + + @Schema(description = "안건 제목", example = "브레인스토밍") + private String title; + + @DateTimeFormat(pattern = "HH:mm:ss") + @Schema(description = "안건 소요 시간", example = "01:20:00") + private LocalTime allocatedDuration; + + @Schema(description = "안건 순서", example = "1") + private Integer orderNum; +} diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java new file mode 100644 index 0000000..6139807 --- /dev/null +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java @@ -0,0 +1,35 @@ +package org.dnd.timeet.agenda.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import org.dnd.timeet.agenda.domain.Agenda; +import org.dnd.timeet.common.utils.DurationUtils; + +@Schema(description = "안건 수정 응답") +@Getter +@Setter +public class AgendaPatchResponse { + + @Schema(description = "안건 id", example = "12") + private Long agendaId; + + @Schema(description = "안건 제목", example = "안건 1") + private String title; + + @NotNull(message = "안건 소요 시간은 반드시 입력되어야 합니다") + @Schema(description = "안건 소요 시간", example = "01:20:00") + private String allocatedDuration; + + @Schema(description = "안건 순서", example = "1") + private Integer orderNum; + + public AgendaPatchResponse(Agenda agenda) { + this.agendaId = agenda.getId(); + this.title = agenda.getTitle(); + this.allocatedDuration = DurationUtils.formatDuration(agenda.getAllocatedDuration()); + this.orderNum = agenda.getOrderNum(); + } + +} \ No newline at end of file From cc7148a4fce930730c5abc5c8556303eb4437a31 Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 18:59:12 +0900 Subject: [PATCH 08/23] [#-] refactor: Enhance convertLocalTimeToDuration to include seconds in calculations --- src/main/java/org/dnd/timeet/common/utils/DurationUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/dnd/timeet/common/utils/DurationUtils.java b/src/main/java/org/dnd/timeet/common/utils/DurationUtils.java index 699e891..71de09c 100644 --- a/src/main/java/org/dnd/timeet/common/utils/DurationUtils.java +++ b/src/main/java/org/dnd/timeet/common/utils/DurationUtils.java @@ -12,8 +12,8 @@ public class DurationUtils { * @return Duration 객체 */ public static Duration convertLocalTimeToDuration(LocalTime time) { - int totalMinutes = time.getHour() * 60 + time.getMinute(); - return Duration.ofMinutes(totalMinutes); + int totalSeconds = time.toSecondOfDay(); // 시, 분, 초를 모두 초 단위로 변환 + return Duration.ofSeconds(totalSeconds); // 변환된 초를 바탕으로 Duration 생성 } /** From 1f5d808f2ee39b22e67a33a9a678a0d0e6a0d216 Mon Sep 17 00:00:00 2001 From: FacerAin Date: Tue, 20 Feb 2024 22:04:51 +0900 Subject: [PATCH 09/23] [#62] feat: Add google oauth2 Login --- .../org/dnd/timeet/member/domain/Member.java | 6 ++--- .../member/domain/MemberRepository.java | 2 +- .../dnd/timeet/oauth/info/OAuth2UserInfo.java | 2 +- .../oauth/info/OAuth2UserInfoFactory.java | 3 ++- .../oauth/info/impl/GoogleOAuth2UserInfo.java | 27 +++++++++++++++++++ .../oauth/info/impl/KakaoOAuth2UserInfo.java | 4 +-- 6 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/dnd/timeet/oauth/info/impl/GoogleOAuth2UserInfo.java diff --git a/src/main/java/org/dnd/timeet/member/domain/Member.java b/src/main/java/org/dnd/timeet/member/domain/Member.java index 5ffdae1..f7ab39a 100644 --- a/src/main/java/org/dnd/timeet/member/domain/Member.java +++ b/src/main/java/org/dnd/timeet/member/domain/Member.java @@ -38,7 +38,7 @@ public class Member extends BaseEntity { private String imageUrl; @Column(length = 100, nullable = false, name = "oauth_id") - private Long oauthId; + private String oauthId; @Enumerated(EnumType.STRING) @Column(length = 50, nullable = false) @@ -46,13 +46,13 @@ public class Member extends BaseEntity { @Column(length = 255) private String fcmToken; - + @OneToMany(mappedBy = "member", fetch = FetchType.EAGER) private Set participations = new HashSet<>(); // MEMO : 필수값들이므로 final 붙임 @Builder - public Member(MemberRole role, String name, String imageUrl, Long oauthId, OAuth2Provider provider) { + public Member(MemberRole role, String name, String imageUrl, String oauthId, OAuth2Provider provider) { this.role = role; this.name = name; this.imageUrl = imageUrl; diff --git a/src/main/java/org/dnd/timeet/member/domain/MemberRepository.java b/src/main/java/org/dnd/timeet/member/domain/MemberRepository.java index f260015..6d103a1 100644 --- a/src/main/java/org/dnd/timeet/member/domain/MemberRepository.java +++ b/src/main/java/org/dnd/timeet/member/domain/MemberRepository.java @@ -8,5 +8,5 @@ public interface MemberRepository extends JpaRepository { Optional findById(Long id); - Optional findByOauthId(Long oauthId); + Optional findByOauthId(String oauthId); } diff --git a/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfo.java b/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfo.java index d1c030f..f686832 100644 --- a/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfo.java +++ b/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfo.java @@ -14,7 +14,7 @@ public Map getAttributes() { return attributes; } - public abstract Long getId(); + public abstract String getId(); public abstract String getName(); diff --git a/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfoFactory.java b/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfoFactory.java index 3992e59..8933dc1 100644 --- a/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfoFactory.java +++ b/src/main/java/org/dnd/timeet/oauth/info/OAuth2UserInfoFactory.java @@ -2,6 +2,7 @@ import java.util.Map; import org.dnd.timeet.oauth.OAuth2Provider; +import org.dnd.timeet.oauth.info.impl.GoogleOAuth2UserInfo; import org.dnd.timeet.oauth.info.impl.KakaoOAuth2UserInfo; public class OAuth2UserInfoFactory { @@ -14,7 +15,7 @@ public static OAuth2UserInfo getOAuth2Userinfo(OAuth2Provider oauth2Provider, Map attributes) { if (oauth2Provider == OAuth2Provider.GOOGLE) { - return null; + return new GoogleOAuth2UserInfo(attributes); } if (oauth2Provider == OAuth2Provider.NAVER) { return null; diff --git a/src/main/java/org/dnd/timeet/oauth/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/org/dnd/timeet/oauth/info/impl/GoogleOAuth2UserInfo.java new file mode 100644 index 0000000..e6aaddc --- /dev/null +++ b/src/main/java/org/dnd/timeet/oauth/info/impl/GoogleOAuth2UserInfo.java @@ -0,0 +1,27 @@ +package org.dnd.timeet.oauth.info.impl; + +import java.util.Map; +import org.dnd.timeet.oauth.info.OAuth2UserInfo; + +public class GoogleOAuth2UserInfo extends OAuth2UserInfo { + + public GoogleOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return (String) attributes.get("sub"); + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } + + @Override + public String getImageUrl() { + return (String) attributes.get("picture"); + } + +} diff --git a/src/main/java/org/dnd/timeet/oauth/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/org/dnd/timeet/oauth/info/impl/KakaoOAuth2UserInfo.java index 8ffb30d..5fdf724 100644 --- a/src/main/java/org/dnd/timeet/oauth/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/org/dnd/timeet/oauth/info/impl/KakaoOAuth2UserInfo.java @@ -16,8 +16,8 @@ public KakaoOAuth2UserInfo(Map attributes) { } @Override - public Long getId() { - return (Long) attributes.get("id"); + public String getId() { + return String.valueOf(attributes.get("id")); } @Override From cea9e0765633f6ff3b71e7756aa0b68228947e35 Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 22:44:04 +0900 Subject: [PATCH 10/23] [#-] feat: Add host information to participant lookup API --- .../meeting/application/MeetingService.java | 25 ++++++++++-- .../meeting/controller/MeetingController.java | 16 ++------ .../dto/MeetingMemberInfoResponse.java | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index f352197..11b5e59 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -18,6 +18,8 @@ import org.dnd.timeet.meeting.domain.Meeting; import org.dnd.timeet.meeting.domain.MeetingRepository; import org.dnd.timeet.meeting.dto.MeetingCreateRequest; +import org.dnd.timeet.meeting.dto.MeetingMemberInfoResponse; +import org.dnd.timeet.meeting.dto.MeetingMemberInfoResponse.MeetingMemberDetailResponse; import org.dnd.timeet.meeting.dto.MeetingRemainingTimeResponse; import org.dnd.timeet.meeting.dto.MeetingReportInfoResponse; import org.dnd.timeet.member.domain.Member; @@ -132,20 +134,35 @@ public void cancelMeeting(Long meetingId) { } @Transactional(readOnly = true) - public List getMeetingMembers(Long meetingId) { + public MeetingMemberInfoResponse getMeetingMembers(Long meetingId) { Meeting meeting = meetingRepository.findByIdWithParticipantsAndMembers(meetingId) .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); // 회의에 참가자가 없는 경우 빈 리스트 반환 if (meeting.getParticipants().isEmpty()) { - return Collections.emptyList(); + return new MeetingMemberInfoResponse(null, Collections.emptyList()); + } + + MeetingMemberDetailResponse hostResponse; + // 방장 정보 추출 + if (meeting.getHostMember() == null) { + hostResponse = new MeetingMemberDetailResponse(null); + } else { + Member hostMember = memberRepository.findById(meeting.getHostMember().getId()) + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("HostMemberId", "Host member not found"))); + hostResponse = new MeetingMemberDetailResponse(hostMember); } // 참가자 목록을 Member 객체의 리스트로 변환하여 반환 - return meeting.getParticipants().stream() + List memberList = meeting.getParticipants().stream() .map(Participant::getMember) - .collect(Collectors.toList()); + .filter(member -> !member.equals(meeting.getHostMember())) // 방장 제외 + .map(MeetingMemberDetailResponse::new) + .toList(); + + return new MeetingMemberInfoResponse(hostResponse, memberList); } @Transactional(readOnly = true) diff --git a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java index 59fc6eb..2947eeb 100644 --- a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java +++ b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java @@ -3,8 +3,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.dnd.timeet.common.security.CustomUserDetails; import org.dnd.timeet.common.security.annotation.ReqUser; @@ -15,12 +13,11 @@ import org.dnd.timeet.meeting.dto.MeetingCreateRequest; import org.dnd.timeet.meeting.dto.MeetingCreateResponse; import org.dnd.timeet.meeting.dto.MeetingInfoResponse; +import org.dnd.timeet.meeting.dto.MeetingMemberInfoResponse; import org.dnd.timeet.meeting.dto.MeetingRemainingTimeResponse; import org.dnd.timeet.meeting.dto.MeetingReportInfoResponse; import org.dnd.timeet.meeting.dto.MeetingReportResponse; import org.dnd.timeet.member.domain.Member; -import org.dnd.timeet.member.dto.MemberInfoListResponse; -import org.dnd.timeet.member.dto.MemberInfoResponse; import org.springframework.http.ResponseEntity; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; @@ -118,16 +115,11 @@ public ResponseEntity deleteMeeting(@PathVariable("meeting-id") Long meetingId) @GetMapping("/{meeting-id}/users") @Operation(summary = "회의 참가자 조회", description = "회의에 참가한 사용자를 조회한다.") - public ResponseEntity> getMeetingMembers( + public ResponseEntity> getMeetingMembers( @PathVariable("meeting-id") Long meetingId) { - List memberInfoList = meetingService.getMeetingMembers(meetingId) - .stream() - .map(MemberInfoResponse::from) - .collect(Collectors.toList()); + MeetingMemberInfoResponse response = meetingService.getMeetingMembers(meetingId); - MemberInfoListResponse memberInfoListResponse = new MemberInfoListResponse(memberInfoList); - - return ResponseEntity.ok(ApiUtils.success(memberInfoListResponse)); + return ResponseEntity.ok(ApiUtils.success(response)); } @DeleteMapping("/{meeting-id}/leave") diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java new file mode 100644 index 0000000..85c9611 --- /dev/null +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java @@ -0,0 +1,40 @@ +package org.dnd.timeet.meeting.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.dnd.timeet.member.domain.Member; + +@Schema(description = "회의 멤버 정보 응답") +@Getter +@Setter +public class MeetingMemberInfoResponse { + + MeetingMemberDetailResponse hostMember; + List members; + + public MeetingMemberInfoResponse(MeetingMemberDetailResponse hostMember, + List members) { + this.hostMember = hostMember; + this.members = members; + } + + @Getter + @Setter + public static class MeetingMemberDetailResponse { + + @Schema(description = "사용자 id", nullable = false, example = "12") + private Long id; + @Schema(description = "사용자 이름", nullable = false, example = "green12") + private String nickname; + + + public MeetingMemberDetailResponse(Member member) { + this.id = member == null ? null : member.getId(); + this.nickname = member == null ? null : member.getName(); + } + } +} + + From 4fa3ea97014716851955fd7eb4a70c6bcf798afb Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 22:48:53 +0900 Subject: [PATCH 11/23] [#-] remove: remove meeting auto-end scheduling due to plan changes --- .../meeting/application/MeetingAsyncService.java | 11 ----------- .../meeting/application/MeetingScheduler.java | 15 --------------- 2 files changed, 26 deletions(-) diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java index 67bd11b..9b6c315 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java @@ -32,15 +32,4 @@ public void startScheduledMeeting(Long meetingId) { logger.error("Error starting scheduled meeting", e); } } - - @Transactional - @Async // 비동기 작업 실행시 발생하는 에러 처리 - public void endScheduledMeeting(Meeting meeting) { - try { - meeting.endMeeting(); - meetingRepository.save(meeting); - } catch (Exception e) { - logger.error("Error starting scheduled meeting", e); - } - } } \ No newline at end of file diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java index 3e3b4aa..a6c91c9 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java @@ -36,19 +36,4 @@ public void scheduleMeetingStart(Long meetingId, LocalDateTime startTime) { meetingAsyncService.startScheduledMeeting(meetingId), delay, TimeUnit.MILLISECONDS); } - @Scheduled(fixedRate = 60000) // 60000ms = 1분 - public void scheduleMeetingEnd() { - List meetingsByStatusInProgress = meetingRepository.findMeetingsByStatusInProgress(); - - LocalDateTime now = LocalDateTime.now(); - - meetingsByStatusInProgress.forEach(meeting -> { - // 남은 시간이 0이거나 음수인 경우 회의를 종료 - Duration remainingTime = meeting.calculateRemainingTime(); - if (remainingTime.isZero() || remainingTime.isNegative()) { - meetingAsyncService.endScheduledMeeting(meeting); - } - }); - } - } From d710069a20247504f14ee9dba9df2270c3d056fa Mon Sep 17 00:00:00 2001 From: Mint Date: Tue, 20 Feb 2024 23:47:46 +0900 Subject: [PATCH 12/23] [#-] remove: Remove meeting start scheduling feature due to plan changes --- .../application/MeetingAsyncService.java | 33 ++++++++----------- .../meeting/application/MeetingScheduler.java | 31 +++++++---------- .../meeting/application/MeetingService.java | 4 --- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java index 9b6c315..ae06eb3 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingAsyncService.java @@ -1,15 +1,10 @@ package org.dnd.timeet.meeting.application; -import java.util.Collections; import lombok.RequiredArgsConstructor; -import org.dnd.timeet.common.exception.NotFoundError; -import org.dnd.timeet.meeting.domain.Meeting; import org.dnd.timeet.meeting.domain.MeetingRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -18,18 +13,18 @@ public class MeetingAsyncService { private final MeetingRepository meetingRepository; private final Logger logger = LoggerFactory.getLogger(MeetingAsyncService.class); - @Transactional - @Async // 비동기 작업 실행시 발생하는 에러 처리 - public void startScheduledMeeting(Long meetingId) { - try { - Meeting meeting = meetingRepository.findById(meetingId) - .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, - Collections.singletonMap("MeetingId", "Meeting not found"))); - - meeting.startMeeting(); - meetingRepository.save(meeting); - } catch (Exception e) { - logger.error("Error starting scheduled meeting", e); - } - } +// @Transactional +// @Async // 비동기 작업 실행시 발생하는 에러 처리 +// public void startScheduledMeeting(Long meetingId) { +// try { +// Meeting meeting = meetingRepository.findById(meetingId) +// .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, +// Collections.singletonMap("MeetingId", "Meeting not found"))); +// +// meeting.startMeeting(); +// meetingRepository.save(meeting); +// } catch (Exception e) { +// logger.error("Error starting scheduled meeting", e); +// } +// } } \ No newline at end of file diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java index a6c91c9..4af799f 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingScheduler.java @@ -1,18 +1,9 @@ package org.dnd.timeet.meeting.application; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.List; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; -import org.dnd.timeet.common.exception.BadRequestError; -import org.dnd.timeet.meeting.domain.Meeting; import org.dnd.timeet.meeting.domain.MeetingRepository; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @EnableScheduling @@ -24,16 +15,16 @@ public class MeetingScheduler { private final ScheduledExecutorService scheduledExecutorService; private final MeetingRepository meetingRepository; - public void scheduleMeetingStart(Long meetingId, LocalDateTime startTime) { - long delay = ChronoUnit.MILLIS.between(LocalDateTime.now(), startTime); - if (delay < 0) { - throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, - Collections.singletonMap("startTime", "startTime is past")); - } - - // 스케줄러 생성 - scheduledExecutorService.schedule(() -> - meetingAsyncService.startScheduledMeeting(meetingId), delay, TimeUnit.MILLISECONDS); - } +// public void scheduleMeetingStart(Long meetingId, LocalDateTime startTime) { +// long delay = ChronoUnit.MILLIS.between(LocalDateTime.now(), startTime); +// if (delay < 0) { +// throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, +// Collections.singletonMap("startTime", "startTime is past")); +// } +// +// // 스케줄러 생성 +// scheduledExecutorService.schedule(() -> +// meetingAsyncService.startScheduledMeeting(meetingId), delay, TimeUnit.MILLISECONDS); +// } } diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index 11b5e59..4b0677e 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -37,7 +37,6 @@ public class MeetingService { private final MeetingRepository meetingRepository; private final ParticipantRepository participantRepository; private final AgendaRepository agendaRepository; - private final MeetingScheduler meetingScheduler; private final MemberRepository memberRepository; @@ -48,9 +47,6 @@ public Meeting createMeeting(MeetingCreateRequest createDto, Member member) { Participant participant = new Participant(meeting, member); participantRepository.save(participant); - // 스케줄러를 통해 회의 시작 시간에 회의 시작 - meetingScheduler.scheduleMeetingStart(meeting.getId(), meeting.getStartTime()); - return meeting; } From fe854e86d495bc000366af1f4354d70e0aa848c7 Mon Sep 17 00:00:00 2001 From: FacerAin Date: Wed, 21 Feb 2024 00:16:14 +0900 Subject: [PATCH 13/23] [#65] feat: Add changeAgendaOrder --- .../agenda/application/AgendaService.java | 29 +++++++++++++++++++ .../agenda/controller/AgendaController.java | 12 ++++++++ .../org/dnd/timeet/agenda/domain/Agenda.java | 3 ++ .../agenda/dto/AgendaActionResponse.java | 3 +- .../agenda/dto/AgendaCreateRequest.java | 5 ---- .../timeet/agenda/dto/AgendaOrderRequest.java | 18 ++++++++++++ 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/dnd/timeet/agenda/dto/AgendaOrderRequest.java diff --git a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java index b172b4b..a03adb7 100644 --- a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java +++ b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java @@ -3,6 +3,7 @@ import java.time.Duration; import java.time.LocalTime; import java.util.Collections; +import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; import org.dnd.timeet.agenda.domain.Agenda; @@ -33,6 +34,7 @@ public class AgendaService { private final AgendaRepository agendaRepository; private final ParticipantRepository participantRepository; + @Transactional public Long createAgenda(Long meetingId, AgendaCreateRequest createDto, Member member) { Meeting meeting = meetingRepository.findById(meetingId) .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, @@ -44,6 +46,8 @@ public Long createAgenda(Long meetingId, AgendaCreateRequest createDto, Member m Collections.singletonMap("MemberId", "Member is not a participant of the meeting"))); Agenda agenda = createDto.toEntity(meeting); + List agendaList = agendaRepository.findByMeetingId(meetingId); + agenda.setOrderNum(agendaList.size() + 1); agenda = agendaRepository.save(agenda); // 회의 시간 추가 @@ -143,6 +147,31 @@ public AgendaInfoResponse findAgendas(Long meetingId) { .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); List agendaList = agendaRepository.findByMeetingId(meetingId); + agendaList.sort(Comparator.comparing(Agenda::getOrderNum)); + return new AgendaInfoResponse(meeting, agendaList); + } + + public AgendaInfoResponse changeAgendaOrder(Long meetingId, List agendaIds) { + Meeting meeting = meetingRepository.findById(meetingId) + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MeetingId", "Meeting not found"))); + List agendaList = agendaRepository.findByMeetingId(meetingId); + + if (agendaList.size() != agendaIds.stream().distinct().count()) { + throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, + Collections.singletonMap("AgendaIds", "Agenda Ids are not unique")); + } + + for (int i = 0; i < agendaIds.size(); i++) { + Long agendaId = agendaIds.get(i); + Agenda agenda = agendaList.stream() + .filter(a -> a.getId().equals(agendaId)) + .findFirst() + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("AgendaId", "Agenda Id " + agendaId + " not found"))); + agenda.setOrderNum(i + 1); + } + agendaList.sort(Comparator.comparing(Agenda::getOrderNum)); return new AgendaInfoResponse(meeting, agendaList); } diff --git a/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java b/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java index 13b8769..43b5de8 100644 --- a/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java +++ b/src/main/java/org/dnd/timeet/agenda/controller/AgendaController.java @@ -9,6 +9,7 @@ import org.dnd.timeet.agenda.dto.AgendaActionResponse; import org.dnd.timeet.agenda.dto.AgendaCreateRequest; import org.dnd.timeet.agenda.dto.AgendaInfoResponse; +import org.dnd.timeet.agenda.dto.AgendaOrderRequest; import org.dnd.timeet.common.security.CustomUserDetails; import org.dnd.timeet.common.utils.ApiUtils; import org.dnd.timeet.common.utils.ApiUtils.ApiResult; @@ -19,6 +20,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -81,4 +83,14 @@ public ResponseEntity deleteAgenda( return ResponseEntity.noContent().build(); } + + @PatchMapping("/{meeting-id}/agendas/order") + @Operation(summary = "안건 순서 변경", description = "안건의 순서를 변경한다.") + public ResponseEntity> changeAgendaOrder( + @PathVariable("meeting-id") Long meetingId, + @RequestBody @Valid AgendaOrderRequest agendaOrderRequest) { + AgendaInfoResponse agendaInfoResponse = agendaService.changeAgendaOrder(meetingId, + agendaOrderRequest.getAgendaIds()); + return ResponseEntity.ok(ApiUtils.success(agendaInfoResponse)); + } } diff --git a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java index 2c50b4f..20f3bdb 100644 --- a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java +++ b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java @@ -75,6 +75,9 @@ public Agenda(Meeting meeting, String title, AgendaType type, Duration allocated this.orderNum = orderNum; } + public void setOrderNum(Integer orderNum) { + this.orderNum = orderNum; + } public void start() { validateTransition(AgendaStatus.PENDING); diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java index 94f195e..ca6a8b7 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java @@ -35,8 +35,7 @@ public AgendaActionResponse(Agenda agenda, Duration currentDuration, Duration re this.currentDuration = DurationUtils.formatDuration(currentDuration); this.remainingDuration = DurationUtils.formatDuration(remainingDuration); - - this.orderNum = agenda.getOrderNum(); + this.timestamp = DateTimeUtils.formatLocalDateTime(LocalDateTime.now()); } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java index abeddc4..1882702 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java @@ -32,17 +32,12 @@ public class AgendaCreateRequest { @Schema(description = "안건 소요 시간", example = "01:20:00") private LocalTime allocatedDuration; - @NotNull(message = "안건 순서는 반드시 입력되어야 합니다") - @Schema(description = "안건 순서", example = "1") - private Integer orderNum; - public Agenda toEntity(Meeting meeting) { return Agenda.builder() .meeting(meeting) .title(this.title) .type(this.type.equals("AGENDA") ? AgendaType.AGENDA : AgendaType.BREAK) .allocatedDuration(DurationUtils.convertLocalTimeToDuration(this.allocatedDuration)) - .orderNum(this.orderNum) .build(); } } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaOrderRequest.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaOrderRequest.java new file mode 100644 index 0000000..70d9701 --- /dev/null +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaOrderRequest.java @@ -0,0 +1,18 @@ +package org.dnd.timeet.agenda.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Schema(description = "안건 순서 변경 요청") +@Getter +@Setter +@NoArgsConstructor +public class AgendaOrderRequest { + + @Schema(description = "안건 ID 리스트", example = "[2,1,3]") + private List agendaIds; + +} From 0c6aa2c2691172b37a266f80dde77a0f1fb69a31 Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 00:28:19 +0900 Subject: [PATCH 14/23] [#-] feat: Implement starting automatically with the first agenda initiation --- .../agenda/application/AgendaService.java | 60 +++++++++++++------ .../agenda/domain/AgendaRepository.java | 5 ++ .../timeet/agenda/dto/AgendaInfoResponse.java | 4 ++ .../meeting/application/MeetingService.java | 16 +++-- .../dnd/timeet/meeting/domain/Meeting.java | 10 +++- .../meeting/dto/MeetingInfoResponse.java | 5 +- .../domain/ParticipantRepository.java | 3 +- 7 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java index b5b4bbc..71e9c48 100644 --- a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java +++ b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java @@ -41,9 +41,18 @@ public Long createAgenda(Long meetingId, AgendaCreateRequest createDto, Member m Collections.singletonMap("MeetingId", "Meeting not found"))); // 회의에 참가한 멤버인지 확인 - participantRepository.findByMeetingIdAndMemberId(meetingId, member.getId()) - .orElseThrow(() -> new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, - Collections.singletonMap("MemberId", "Member is not a participant of the meeting"))); + boolean isParticipantExists = participantRepository.existsByMeetingIdAndMemberId(meetingId, member.getId()); + if (!isParticipantExists) { + throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, + Collections.singletonMap("MemberId", "Member is not a participant of the meeting")); + } + + // OrderNum 중복 검사 + boolean isOrderNumExists = agendaRepository.existsByMeetingIdAndOrderNum(meetingId, createDto.getOrderNum()); + if (isOrderNumExists) { + throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, + Collections.singletonMap("OrderNum", "OrderNum already exists in the meeting")); + } Agenda agenda = createDto.toEntity(meeting); agenda = agendaRepository.save(agenda); @@ -61,6 +70,10 @@ public List findAll(Long meetingId) { } public AgendaActionResponse changeAgendaStatus(Long meetingId, Long agendaId, AgendaActionRequest actionRequest) { + Meeting meeting = meetingRepository.findById(meetingId) + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MeetingId", "Meeting not found"))); + Agenda agenda = agendaRepository.findByIdAndMeetingId(agendaId, meetingId) .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("AgendaId", "Agenda not found"))); @@ -75,31 +88,40 @@ public AgendaActionResponse changeAgendaStatus(Long meetingId, Long agendaId, Ag Collections.singletonMap("Action", "Invalid action")); } + // 안건 시작 요청 전 첫 번째 안건이 시작되었는지 확인 + if (action == AgendaAction.START && agenda.getOrderNum() != 1) { + // 첫 번째 안건의 시작 여부 확인 + boolean isFirstAgendaStarted = agendaRepository.existsByMeetingIdAndOrderNumAndStatus( + meetingId, 1, AgendaStatus.COMPLETED); + + if (!isFirstAgendaStarted) { + throw new BadRequestError(BadRequestError.ErrorCode.WRONG_REQUEST_TRANSMISSION, + Collections.singletonMap("AgendaOrder", "First agenda has not been started yet")); + } + } + switch (action) { - case START: + case START -> { + // 첫번째 안건의 시작 시간을 회의 시작 시간으로 설정 + meeting.updateStartTimeOnFirstAgendaStart(agenda); agenda.start(); - break; - case PAUSE: - agenda.pause(); - break; - case RESUME: - agenda.resume(); - break; - case END: - agenda.complete(); - break; - case MODIFY: + } + case PAUSE -> agenda.pause(); + case RESUME -> agenda.resume(); + case END -> agenda.complete(); + case MODIFY -> { LocalTime modifiedDuration = LocalTime.parse(actionRequest.getModifiedDuration()); Duration duration = DurationUtils.convertLocalTimeToDuration(modifiedDuration); agenda.extendDuration(duration); // 회의 시간 추가 addMeetingTotalActualDuration(meetingId, duration); - break; - default: - throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, - Collections.singletonMap("Action", "Invalid action")); + } + default -> throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, + Collections.singletonMap("Action", "Invalid action")); } + // 변경 사항 저장 Agenda savedAgenda = agendaRepository.save(agenda); + meetingRepository.save(meeting); Duration currentDuration = savedAgenda.calculateCurrentDuration(); Duration remainingDuration = agenda.calculateRemainingTime(); diff --git a/src/main/java/org/dnd/timeet/agenda/domain/AgendaRepository.java b/src/main/java/org/dnd/timeet/agenda/domain/AgendaRepository.java index 8d08a36..9215595 100644 --- a/src/main/java/org/dnd/timeet/agenda/domain/AgendaRepository.java +++ b/src/main/java/org/dnd/timeet/agenda/domain/AgendaRepository.java @@ -9,4 +9,9 @@ public interface AgendaRepository extends JpaRepository { List findByMeetingId(Long meetingId); Optional findByIdAndMeetingId(Long agendaId, Long meetingId); + + boolean existsByMeetingIdAndOrderNum(Long meetingId, Integer orderNum); + + boolean existsByMeetingIdAndOrderNumAndStatus(Long meetingId, Integer orderNum, AgendaStatus status); + } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java index d8a66a2..43739b7 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java @@ -54,6 +54,9 @@ public class AgendaResponse { @Schema(description = "안건 상태", example = "INPROGRESS") private String status; + @Schema(description = "안건 순서 번호", example = "1") + private Integer orderNum; + public AgendaResponse(Agenda agenda, Duration currentDuration, Duration remainingDuration) { this.agendaId = agenda.getId(); this.title = agenda.getTitle(); @@ -61,6 +64,7 @@ public AgendaResponse(Agenda agenda, Duration currentDuration, Duration remainin this.currentDuration = DurationUtils.formatDuration(currentDuration); this.remainingDuration = DurationUtils.formatDuration(remainingDuration); this.status = agenda.getStatus().name(); + this.orderNum = agenda.getOrderNum(); } } } \ No newline at end of file diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index 4b0677e..a240941 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -54,6 +54,11 @@ public void endMeeting(Long meetingId, Long memberId) { Meeting meeting = meetingRepository.findById(meetingId) .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); + // 회의의 방장이 존재하는지 확인 + if (meeting.getHostMember() == null) { + throw new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("HostMemberId", "Host member not found")); + } // 회의의 방장인지 확인 if (!Objects.equals(meeting.getHostMember().getId(), memberId)) { throw new ForbiddenError(ForbiddenError.ErrorCode.ROLE_BASED_ACCESS_ERROR, @@ -174,16 +179,19 @@ public void leaveMeeting(Long meetingId, Long memberId) { Meeting meeting = meetingRepository.findById(meetingId) .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); - memberRepository.findById(memberId) - .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, - Collections.singletonMap("MemberId", "Member not found"))); + + boolean memberExists = memberRepository.existsById(memberId); + if (!memberExists) { + throw new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MemberId", "Member not found")); + } Participant participant = participantRepository.findByMeetingIdAndMemberId(meetingId, memberId) .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("ParticipantId", "Participant not found"))); // 방장이 나가는 경우 새로운 방장 지정 - if (meeting.getHostMember().getId().equals(memberId)) { + if (meeting.getHostMember() == null || meeting.getHostMember().getId().equals(memberId)) { meeting.assignNewHostRandomly(); meetingRepository.save(meeting); } diff --git a/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java b/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java index 15996bb..5344b0d 100644 --- a/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java +++ b/src/main/java/org/dnd/timeet/meeting/domain/Meeting.java @@ -22,6 +22,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.dnd.timeet.agenda.domain.Agenda; import org.dnd.timeet.common.domain.AuditableEntity; import org.dnd.timeet.common.exception.BadRequestError; import org.dnd.timeet.common.exception.BadRequestError.ErrorCode; @@ -87,9 +88,12 @@ public Meeting(Member hostMember, String title, LocalDateTime startTime, Duratio this.imgNum = imgNum; } - - public void startMeeting() { - this.status = MeetingStatus.INPROGRESS; + public void updateStartTimeOnFirstAgendaStart(Agenda agenda) { + if (agenda.getOrderNum() == 1) { + // 회의 시작 + this.startTime = LocalDateTime.now(); + this.status = MeetingStatus.INPROGRESS; + } } // 회의 종료 버튼 누르거나 소요 시간이 끝날 경우 diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java index 60ed549..7307a34 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.dnd.timeet.common.utils.DateTimeUtils; import org.dnd.timeet.common.utils.DurationUtils; import org.dnd.timeet.meeting.domain.Meeting; @@ -28,7 +29,7 @@ public class MeetingInfoResponse { @Schema(description = "회의 방장 멤버 ID", example = "13") private Long hostMemberId; - @Schema(description = "회의 시작 일자", example = "2024-01-11T13:20") + @Schema(description = "회의 시작 일자", example = "2024-01-11T13:20:00") private String startTime; @Schema(description = "예상 소요시간", example = "03:00:00") @@ -67,7 +68,7 @@ public static MeetingInfoResponse from(Meeting meeting) { // 매개변수로부 .description(meeting.getDescription()) .meetingStatus(meeting.getStatus().name()) .hostMemberId(meeting.getHostMember() == null ? null : meeting.getHostMember().getId()) - .startTime(meeting.getStartTime().toString()) + .startTime(DateTimeUtils.formatLocalDateTime(meeting.getStartTime())) .totalEstimatedDuration(meeting.getTotalEstimatedDuration()) .remainingTime(meeting.calculateRemainingTime()) .actualTotalDuration(meeting.calculateCurrentDuration()) diff --git a/src/main/java/org/dnd/timeet/participant/domain/ParticipantRepository.java b/src/main/java/org/dnd/timeet/participant/domain/ParticipantRepository.java index dce4b93..36a78c4 100644 --- a/src/main/java/org/dnd/timeet/participant/domain/ParticipantRepository.java +++ b/src/main/java/org/dnd/timeet/participant/domain/ParticipantRepository.java @@ -1,12 +1,11 @@ package org.dnd.timeet.participant.domain; import java.util.Optional; -import org.dnd.timeet.participant.domain.Participant; import org.springframework.data.jpa.repository.JpaRepository; public interface ParticipantRepository extends JpaRepository { Optional findByMeetingIdAndMemberId(Long meetingId, Long memberId); -// Optional findByUserId(Long id); + boolean existsByMeetingIdAndMemberId(Long meetingId, Long memberId); } From 88dcac35bd14174f54fd85b8d8d800560d06a5bc Mon Sep 17 00:00:00 2001 From: FacerAin Date: Wed, 21 Feb 2024 01:29:50 +0900 Subject: [PATCH 15/23] [#65] feat: Fix changeAgendaOrder for verify agendaIds size --- .../org/dnd/timeet/agenda/application/AgendaService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java index a03adb7..7afe148 100644 --- a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java +++ b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java @@ -157,6 +157,11 @@ public AgendaInfoResponse changeAgendaOrder(Long meetingId, List agendaIds Collections.singletonMap("MeetingId", "Meeting not found"))); List agendaList = agendaRepository.findByMeetingId(meetingId); + if (agendaList.size() != agendaIds.size()) { + throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, + Collections.singletonMap("AgendaIds", "Agenda Ids size is not matched")); + } + if (agendaList.size() != agendaIds.stream().distinct().count()) { throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, Collections.singletonMap("AgendaIds", "Agenda Ids are not unique")); From 2a07f897152a6211ad0e8d681e18dad2029194ff Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 01:32:49 +0900 Subject: [PATCH 16/23] [#-] remove: Remove orderNum from request and response dto --- .../org/dnd/timeet/agenda/application/AgendaService.java | 9 +-------- src/main/java/org/dnd/timeet/agenda/domain/Agenda.java | 3 +-- .../org/dnd/timeet/agenda/dto/AgendaActionResponse.java | 2 -- .../org/dnd/timeet/agenda/dto/AgendaCreateRequest.java | 5 ----- .../org/dnd/timeet/agenda/dto/AgendaInfoResponse.java | 4 ---- .../org/dnd/timeet/agenda/dto/AgendaPatchRequest.java | 3 --- .../org/dnd/timeet/agenda/dto/AgendaPatchResponse.java | 4 ---- 7 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java index 71e9c48..d2c597d 100644 --- a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java +++ b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java @@ -47,13 +47,6 @@ public Long createAgenda(Long meetingId, AgendaCreateRequest createDto, Member m Collections.singletonMap("MemberId", "Member is not a participant of the meeting")); } - // OrderNum 중복 검사 - boolean isOrderNumExists = agendaRepository.existsByMeetingIdAndOrderNum(meetingId, createDto.getOrderNum()); - if (isOrderNumExists) { - throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, - Collections.singletonMap("OrderNum", "OrderNum already exists in the meeting")); - } - Agenda agenda = createDto.toEntity(meeting); agenda = agendaRepository.save(agenda); @@ -188,7 +181,7 @@ public AgendaPatchResponse patchAgenda(Long meetingId, Long agendaId, AgendaPatc } agenda.update(patchRequest.getTitle(), - DurationUtils.convertLocalTimeToDuration(patchRequest.getAllocatedDuration()), patchRequest.getOrderNum()); + DurationUtils.convertLocalTimeToDuration(patchRequest.getAllocatedDuration())); return new AgendaPatchResponse(agenda); } diff --git a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java index 9fcfed7..43d2af2 100644 --- a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java +++ b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java @@ -158,10 +158,9 @@ public void cancel() { this.delete(); } - public void update(String title, Duration allocatedDuration, Integer orderNum) { + public void update(String title, Duration allocatedDuration) { this.title = title; this.allocatedDuration = allocatedDuration; - this.orderNum = orderNum; } } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java index 94f195e..62f694a 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionResponse.java @@ -24,7 +24,6 @@ public class AgendaActionResponse { private String currentDuration; // 현재까지 진행된 시간 private String remainingDuration; // 남은 시간 - private Integer orderNum; private String timestamp; // 수정 시간 public AgendaActionResponse(Agenda agenda, Duration currentDuration, Duration remainingDuration) { @@ -36,7 +35,6 @@ public AgendaActionResponse(Agenda agenda, Duration currentDuration, Duration re this.currentDuration = DurationUtils.formatDuration(currentDuration); this.remainingDuration = DurationUtils.formatDuration(remainingDuration); - this.orderNum = agenda.getOrderNum(); this.timestamp = DateTimeUtils.formatLocalDateTime(LocalDateTime.now()); } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java index abeddc4..1882702 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaCreateRequest.java @@ -32,17 +32,12 @@ public class AgendaCreateRequest { @Schema(description = "안건 소요 시간", example = "01:20:00") private LocalTime allocatedDuration; - @NotNull(message = "안건 순서는 반드시 입력되어야 합니다") - @Schema(description = "안건 순서", example = "1") - private Integer orderNum; - public Agenda toEntity(Meeting meeting) { return Agenda.builder() .meeting(meeting) .title(this.title) .type(this.type.equals("AGENDA") ? AgendaType.AGENDA : AgendaType.BREAK) .allocatedDuration(DurationUtils.convertLocalTimeToDuration(this.allocatedDuration)) - .orderNum(this.orderNum) .build(); } } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java index 43739b7..d8a66a2 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java @@ -54,9 +54,6 @@ public class AgendaResponse { @Schema(description = "안건 상태", example = "INPROGRESS") private String status; - @Schema(description = "안건 순서 번호", example = "1") - private Integer orderNum; - public AgendaResponse(Agenda agenda, Duration currentDuration, Duration remainingDuration) { this.agendaId = agenda.getId(); this.title = agenda.getTitle(); @@ -64,7 +61,6 @@ public AgendaResponse(Agenda agenda, Duration currentDuration, Duration remainin this.currentDuration = DurationUtils.formatDuration(currentDuration); this.remainingDuration = DurationUtils.formatDuration(remainingDuration); this.status = agenda.getStatus().name(); - this.orderNum = agenda.getOrderNum(); } } } \ No newline at end of file diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java index 4a68335..9568eef 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchRequest.java @@ -19,7 +19,4 @@ public class AgendaPatchRequest { @DateTimeFormat(pattern = "HH:mm:ss") @Schema(description = "안건 소요 시간", example = "01:20:00") private LocalTime allocatedDuration; - - @Schema(description = "안건 순서", example = "1") - private Integer orderNum; } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java index 6139807..a4863e1 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaPatchResponse.java @@ -22,14 +22,10 @@ public class AgendaPatchResponse { @Schema(description = "안건 소요 시간", example = "01:20:00") private String allocatedDuration; - @Schema(description = "안건 순서", example = "1") - private Integer orderNum; - public AgendaPatchResponse(Agenda agenda) { this.agendaId = agenda.getId(); this.title = agenda.getTitle(); this.allocatedDuration = DurationUtils.formatDuration(agenda.getAllocatedDuration()); - this.orderNum = agenda.getOrderNum(); } } \ No newline at end of file From c20bfaf5e9d15d59f20fa352382cd8813882ab87 Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 15:52:39 +0900 Subject: [PATCH 17/23] [#-] feat: Update meeting remaining time tp current duration --- .../timeet/agenda/dto/AgendaInfoResponse.java | 6 +++--- .../meeting/application/MeetingService.java | 6 +++--- .../meeting/controller/MeetingController.java | 12 ++++++------ ...java => MeetingCurrentDurationResponse.java} | 16 ++++++++-------- .../timeet/meeting/dto/MeetingInfoResponse.java | 17 ++++++----------- 5 files changed, 26 insertions(+), 31 deletions(-) rename src/main/java/org/dnd/timeet/meeting/dto/{MeetingRemainingTimeResponse.java => MeetingCurrentDurationResponse.java} (56%) diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java index d8a66a2..9b651f3 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaInfoResponse.java @@ -17,14 +17,14 @@ public class AgendaInfoResponse { @Schema(description = "회의 id", example = "12L") private Long meetingId; - @Schema(description = "회의 남은 시간", example = "00:03:00") - private String remainingTime; + @Schema(description = "회의 소요 시간", example = "00:03:00") + private String currentDuration; private List agendaResponse; public AgendaInfoResponse(Meeting meeting, List agendaList) { this.meetingId = meeting.getId(); - this.remainingTime = DurationUtils.formatDuration(meeting.calculateRemainingTime()); + this.currentDuration = DurationUtils.formatDuration(meeting.calculateCurrentDuration()); this.agendaResponse = agendaList.stream() .map(agenda -> new AgendaResponse(agenda, agenda.calculateCurrentDuration(), agenda.calculateRemainingTime())) diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index a240941..d42d28d 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -18,9 +18,9 @@ import org.dnd.timeet.meeting.domain.Meeting; import org.dnd.timeet.meeting.domain.MeetingRepository; import org.dnd.timeet.meeting.dto.MeetingCreateRequest; +import org.dnd.timeet.meeting.dto.MeetingCurrentDurationResponse; import org.dnd.timeet.meeting.dto.MeetingMemberInfoResponse; import org.dnd.timeet.meeting.dto.MeetingMemberInfoResponse.MeetingMemberDetailResponse; -import org.dnd.timeet.meeting.dto.MeetingRemainingTimeResponse; import org.dnd.timeet.meeting.dto.MeetingReportInfoResponse; import org.dnd.timeet.member.domain.Member; import org.dnd.timeet.member.domain.MemberRepository; @@ -167,12 +167,12 @@ public MeetingMemberInfoResponse getMeetingMembers(Long meetingId) { } @Transactional(readOnly = true) - public MeetingRemainingTimeResponse getRemainingTime(Long meetingId) { + public MeetingCurrentDurationResponse getCurrentDuration(Long meetingId) { Meeting meeting = meetingRepository.findById(meetingId) .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); - return MeetingRemainingTimeResponse.from(meeting); + return MeetingCurrentDurationResponse.from(meeting); } public void leaveMeeting(Long meetingId, Long memberId) { diff --git a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java index 2947eeb..6b491b3 100644 --- a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java +++ b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java @@ -12,9 +12,9 @@ import org.dnd.timeet.meeting.domain.Meeting; import org.dnd.timeet.meeting.dto.MeetingCreateRequest; import org.dnd.timeet.meeting.dto.MeetingCreateResponse; +import org.dnd.timeet.meeting.dto.MeetingCurrentDurationResponse; import org.dnd.timeet.meeting.dto.MeetingInfoResponse; import org.dnd.timeet.meeting.dto.MeetingMemberInfoResponse; -import org.dnd.timeet.meeting.dto.MeetingRemainingTimeResponse; import org.dnd.timeet.meeting.dto.MeetingReportInfoResponse; import org.dnd.timeet.meeting.dto.MeetingReportResponse; import org.dnd.timeet.member.domain.Member; @@ -85,12 +85,12 @@ public ResponseEntity> getTimerById(@PathVariable return ResponseEntity.ok(ApiUtils.success(meetingInfoResponse)); } - @Operation(summary = "남은 시간 조회", description = "웹소켓을 통해 특정 회의의 남은 시간을 조회한다.") - @MessageMapping("/meeting/{meeting-id}/remaining-time") - @SendTo("/topic/meeting/{meeting-id}/remaining-time") - public ResponseEntity> getRemainingTime( + @Operation(summary = "소요 시간 조회", description = "웹소켓을 통해 회의의 소요 시간을 조회한다.") + @MessageMapping("/meeting/{meeting-id}/current-duration") + @SendTo("/topic/meeting/{meeting-id}/current-duration") + public ResponseEntity> getCurrentDuration( @DestinationVariable("meeting-id") Long meetingId) { - MeetingRemainingTimeResponse response = meetingService.getRemainingTime(meetingId); + MeetingCurrentDurationResponse response = meetingService.getCurrentDuration(meetingId); return ResponseEntity.ok(ApiUtils.success(response)); } diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingRemainingTimeResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingCurrentDurationResponse.java similarity index 56% rename from src/main/java/org/dnd/timeet/meeting/dto/MeetingRemainingTimeResponse.java rename to src/main/java/org/dnd/timeet/meeting/dto/MeetingCurrentDurationResponse.java index e4a9ce2..f6697ba 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingRemainingTimeResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingCurrentDurationResponse.java @@ -10,7 +10,7 @@ @Schema(description = "회의 정보 응답") @Getter @Setter -public class MeetingRemainingTimeResponse { +public class MeetingCurrentDurationResponse { @Schema(description = "회의 id", example = "12L") private Long meetingId; @@ -18,22 +18,22 @@ public class MeetingRemainingTimeResponse { @Schema(description = "회의 상태", example = "SCHEDULED") private String meetingStatus; - @Schema(description = "회의 남은 시간", example = "00:03:00") - private String remainingTime; + @Schema(description = "회의 소요 시간", example = "00:03:00") + private String currentDuration; @Builder - public MeetingRemainingTimeResponse(Long meetingId, String meetingStatus, String remainingTime) { + public MeetingCurrentDurationResponse(Long meetingId, String meetingStatus, String currentDuration) { this.meetingId = meetingId; this.meetingStatus = meetingStatus; - this.remainingTime = remainingTime; + this.currentDuration = currentDuration; } - public static MeetingRemainingTimeResponse from(Meeting meeting) { // 매개변수로부터 객체를 생성하는 팩토리 메서드 - return MeetingRemainingTimeResponse + public static MeetingCurrentDurationResponse from(Meeting meeting) { // 매개변수로부터 객체를 생성하는 팩토리 메서드 + return MeetingCurrentDurationResponse .builder() .meetingId(meeting.getId()) .meetingStatus(meeting.getStatus().name()) - .remainingTime(DurationUtils.formatDuration(meeting.calculateRemainingTime())) + .currentDuration(DurationUtils.formatDuration(meeting.calculateCurrentDuration())) .build(); } } \ No newline at end of file diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java index 7307a34..fc3f30b 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingInfoResponse.java @@ -35,11 +35,8 @@ public class MeetingInfoResponse { @Schema(description = "예상 소요시간", example = "03:00:00") private String totalEstimatedDuration; - @Schema(description = "회의 남은 시간", example = "00:03:00") - private String remainingTime; - - @Schema(description = "회의 실제 소요 시간", example = "03:03:00") - private String actualTotalDuration; + @Schema(description = "회의 소요 시간", example = "03:03:00") + private String currentDuration; @Schema(description = "썸네일 이미지 번호", example = "1") private Integer imgNum; @@ -47,8 +44,8 @@ public class MeetingInfoResponse { @Builder public MeetingInfoResponse(Long meetingId, String title, String description, String meetingStatus, Long hostMemberId, - String startTime, Duration totalEstimatedDuration, Duration remainingTime, - Duration actualTotalDuration, Integer imgNum) { + String startTime, Duration totalEstimatedDuration, + Duration currentDuration, Integer imgNum) { this.meetingId = meetingId; this.title = title; this.description = description; @@ -56,8 +53,7 @@ public MeetingInfoResponse(Long meetingId, String title, String description, Str this.hostMemberId = hostMemberId; this.startTime = startTime; this.totalEstimatedDuration = DurationUtils.formatDuration(totalEstimatedDuration); - this.remainingTime = DurationUtils.formatDuration(remainingTime); - this.actualTotalDuration = DurationUtils.formatDuration(actualTotalDuration); + this.currentDuration = DurationUtils.formatDuration(currentDuration); this.imgNum = imgNum; } @@ -70,8 +66,7 @@ public static MeetingInfoResponse from(Meeting meeting) { // 매개변수로부 .hostMemberId(meeting.getHostMember() == null ? null : meeting.getHostMember().getId()) .startTime(DateTimeUtils.formatLocalDateTime(meeting.getStartTime())) .totalEstimatedDuration(meeting.getTotalEstimatedDuration()) - .remainingTime(meeting.calculateRemainingTime()) - .actualTotalDuration(meeting.calculateCurrentDuration()) + .currentDuration(meeting.calculateCurrentDuration()) .imgNum(meeting.getImgNum()) .build(); } From 50f5e06bd99a93ab5207ad3632e569a4db7ab2f2 Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 16:36:47 +0900 Subject: [PATCH 18/23] [#-] feat: Added a boolean value to identify whether a participant is the host --- .../meeting/application/MeetingService.java | 31 ++++++++++++------- .../meeting/controller/MeetingController.java | 5 +-- .../dto/MeetingMemberInfoResponse.java | 19 +++++++++--- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java index d42d28d..9c428e2 100644 --- a/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java +++ b/src/main/java/org/dnd/timeet/meeting/application/MeetingService.java @@ -135,35 +135,44 @@ public void cancelMeeting(Long meetingId) { } @Transactional(readOnly = true) - public MeetingMemberInfoResponse getMeetingMembers(Long meetingId) { + public MeetingMemberInfoResponse getMeetingMembers(Long meetingId, Long memberId) { Meeting meeting = meetingRepository.findByIdWithParticipantsAndMembers(meetingId) - .orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND, + .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("MeetingId", "Meeting not found"))); + boolean memberExists = memberRepository.existsById(memberId); + if (!memberExists) { + throw new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, + Collections.singletonMap("MemberId", "Member not found")); + } + // 회의에 참가자가 없는 경우 빈 리스트 반환 if (meeting.getParticipants().isEmpty()) { - return new MeetingMemberInfoResponse(null, Collections.emptyList()); + return new MeetingMemberInfoResponse(Collections.emptyList(), null, false); } + // 참가자 목록을 Member 객체의 리스트로 변환하여 반환 + List memberList = meeting.getParticipants().stream() + .map(Participant::getMember) + .filter(member -> !member.equals(meeting.getHostMember())) // 방장 제외 + .map(MeetingMemberDetailResponse::new) + .toList(); + MeetingMemberDetailResponse hostResponse; // 방장 정보 추출 if (meeting.getHostMember() == null) { hostResponse = new MeetingMemberDetailResponse(null); + + return new MeetingMemberInfoResponse(memberList, hostResponse, false); } else { Member hostMember = memberRepository.findById(meeting.getHostMember().getId()) .orElseThrow(() -> new NotFoundError(ErrorCode.RESOURCE_NOT_FOUND, Collections.singletonMap("HostMemberId", "Host member not found"))); hostResponse = new MeetingMemberDetailResponse(hostMember); - } - // 참가자 목록을 Member 객체의 리스트로 변환하여 반환 - List memberList = meeting.getParticipants().stream() - .map(Participant::getMember) - .filter(member -> !member.equals(meeting.getHostMember())) // 방장 제외 - .map(MeetingMemberDetailResponse::new) - .toList(); + return new MeetingMemberInfoResponse(memberList, hostResponse, hostMember.getId().equals(memberId)); + } - return new MeetingMemberInfoResponse(hostResponse, memberList); } @Transactional(readOnly = true) diff --git a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java index 6b491b3..6cbdcdd 100644 --- a/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java +++ b/src/main/java/org/dnd/timeet/meeting/controller/MeetingController.java @@ -116,8 +116,9 @@ public ResponseEntity deleteMeeting(@PathVariable("meeting-id") Long meetingId) @GetMapping("/{meeting-id}/users") @Operation(summary = "회의 참가자 조회", description = "회의에 참가한 사용자를 조회한다.") public ResponseEntity> getMeetingMembers( - @PathVariable("meeting-id") Long meetingId) { - MeetingMemberInfoResponse response = meetingService.getMeetingMembers(meetingId); + @PathVariable("meeting-id") Long meetingId, + @ReqUser Member member) { + MeetingMemberInfoResponse response = meetingService.getMeetingMembers(meetingId, member.getId()); return ResponseEntity.ok(ApiUtils.success(response)); } diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java index 85c9611..0a4f44d 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java @@ -6,18 +6,27 @@ import lombok.Setter; import org.dnd.timeet.member.domain.Member; -@Schema(description = "회의 멤버 정보 응답") +@Schema(description = "회의 참가자 정보 응답") @Getter @Setter public class MeetingMemberInfoResponse { - MeetingMemberDetailResponse hostMember; + @Schema(description = "방장 제외 참가자 정보") List members; - public MeetingMemberInfoResponse(MeetingMemberDetailResponse hostMember, - List members) { - this.hostMember = hostMember; + @Schema(description = "방장 정보") + MeetingMemberDetailResponse hostMember; + + @Schema(description = "방장 여부", example = "true") + boolean isHost; + + + public MeetingMemberInfoResponse(List members, + MeetingMemberDetailResponse hostMember, + boolean isHost) { this.members = members; + this.hostMember = hostMember; + this.isHost = isHost; } @Getter From 2a12ddec410f68026c16a7b0afc5c580f05e20af Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 16:51:15 +0900 Subject: [PATCH 19/23] [#-] refactor: Standardize time format to include seconds in all requests and responses --- .../dnd/timeet/agenda/dto/AgendaActionRequest.java | 3 ++- .../timeet/agenda/dto/AgendaReportInfoResponse.java | 2 +- .../java/org/dnd/timeet/common/utils/TimeUtils.java | 13 ++++++------- .../timeet/meeting/dto/MeetingCreateRequest.java | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionRequest.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionRequest.java index 1051f34..f1babe7 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionRequest.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaActionRequest.java @@ -10,7 +10,8 @@ @Setter @NoArgsConstructor public class AgendaActionRequest { + private String action; - private String modifiedDuration; // "HH:MM" 형식, optional + private String modifiedDuration; } diff --git a/src/main/java/org/dnd/timeet/agenda/dto/AgendaReportInfoResponse.java b/src/main/java/org/dnd/timeet/agenda/dto/AgendaReportInfoResponse.java index d3f2125..7ebeea3 100644 --- a/src/main/java/org/dnd/timeet/agenda/dto/AgendaReportInfoResponse.java +++ b/src/main/java/org/dnd/timeet/agenda/dto/AgendaReportInfoResponse.java @@ -20,7 +20,7 @@ public class AgendaReportInfoResponse { @Schema(description = "안건 제목", example = "안건1") private String title; - @Schema(description = "소요 시간 차이 (실제 소요 시간 - 예상 소요 시간)", example = "+01:30") + @Schema(description = "소요 시간 차이 (실제 소요 시간 - 예상 소요 시간)", example = "+01:30:00") private String diff; diff --git a/src/main/java/org/dnd/timeet/common/utils/TimeUtils.java b/src/main/java/org/dnd/timeet/common/utils/TimeUtils.java index b3bad51..0a18c6c 100644 --- a/src/main/java/org/dnd/timeet/common/utils/TimeUtils.java +++ b/src/main/java/org/dnd/timeet/common/utils/TimeUtils.java @@ -21,13 +21,12 @@ public static Duration calculateTimeDiff(Duration actualDuration, Duration estim } public static String formatDuration(Duration duration) { - long hours = duration.abs().toHours(); - long minutes = duration.minusHours(duration.toHours()).abs().toMinutes(); - String prefix = "+"; - if (duration.isNegative()) { - prefix = "-"; - } - return prefix + String.format("%02d:%02d", hours, minutes); + long seconds = duration.abs().getSeconds(); + long hours = seconds / 3600; + long minutes = (seconds % 3600) / 60; + long secs = seconds % 60; + String prefix = duration.isNegative() ? "-" : "+"; + return prefix + String.format("%02d:%02d:%02d", hours, minutes, secs); } } diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingCreateRequest.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingCreateRequest.java index 8d088f0..375a778 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingCreateRequest.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingCreateRequest.java @@ -27,16 +27,16 @@ public class MeetingCreateRequest { private String location; @NotNull(message = "회의 시작 시간은 반드시 입력되어야 합니다") - @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") - @Schema(description = "회의 시작 시간", example = "2024-01-11T13:20") + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + @Schema(description = "회의 시작 시간", example = "2024-01-11T13:20:00") private LocalDateTime startTime; @Schema(description = "회의 목표", example = "2개의 사안 모두 해결하기") private String description; @NotNull(message = "예상 소요 시간은 반드시 입력되어야 합니다") - @DateTimeFormat(pattern = "HH:mm") - @Schema(description = "예상 소요 시간", example = "02:00") + @DateTimeFormat(pattern = "HH:mm:ss") + @Schema(description = "예상 소요 시간", example = "02:00:00") private LocalTime estimatedTotalDuration; @NotNull(message = "썸네일은 반드시 입력되어야 합니다") From fd62d71a6c7e7c94d6927ab60daf6ef79ee8edc8 Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 17:11:39 +0900 Subject: [PATCH 20/23] [#-] feat: Generate random imageNum for members --- .../dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java index 0a4f44d..2e9c699 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; +import java.util.Random; import lombok.Getter; import lombok.Setter; import org.dnd.timeet.member.domain.Member; @@ -35,13 +36,18 @@ public static class MeetingMemberDetailResponse { @Schema(description = "사용자 id", nullable = false, example = "12") private Long id; + @Schema(description = "사용자 이름", nullable = false, example = "green12") private String nickname; + @Schema(description = "이미지 번호(1~12 사이 랜덤)", nullable = false, example = "2") + private Long imageNum; + public MeetingMemberDetailResponse(Member member) { this.id = member == null ? null : member.getId(); this.nickname = member == null ? null : member.getName(); + this.imageNum = new Random().nextLong(12) + 1; } } } From 0e8e825fd35f6bfed253c33228c4e6037ad08920 Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 17:50:56 +0900 Subject: [PATCH 21/23] [#-] feat: set initial values of imageNum for members to be between 1 and 12 --- .../meeting/dto/MeetingMemberInfoResponse.java | 3 +-- .../java/org/dnd/timeet/member/domain/Member.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java index 2e9c699..d213297 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; -import java.util.Random; import lombok.Getter; import lombok.Setter; import org.dnd.timeet.member.domain.Member; @@ -47,7 +46,7 @@ public static class MeetingMemberDetailResponse { public MeetingMemberDetailResponse(Member member) { this.id = member == null ? null : member.getId(); this.nickname = member == null ? null : member.getName(); - this.imageNum = new Random().nextLong(12) + 1; + this.imageNum = member.getImageNum(); } } } diff --git a/src/main/java/org/dnd/timeet/member/domain/Member.java b/src/main/java/org/dnd/timeet/member/domain/Member.java index f7ab39a..336bef4 100644 --- a/src/main/java/org/dnd/timeet/member/domain/Member.java +++ b/src/main/java/org/dnd/timeet/member/domain/Member.java @@ -9,6 +9,7 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.HashSet; +import java.util.Random; import java.util.Set; import lombok.AccessLevel; import lombok.Builder; @@ -50,14 +51,21 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", fetch = FetchType.EAGER) private Set participations = new HashSet<>(); - // MEMO : 필수값들이므로 final 붙임 + @Column(nullable = false, name = "image_num") + private Long imageNum = new Random().nextLong(12) + 1; + @Builder - public Member(MemberRole role, String name, String imageUrl, String oauthId, OAuth2Provider provider) { + public Member(MemberRole role, String name, String imageUrl, String oauthId, OAuth2Provider provider, + String fcmToken, + Set participations, Long imageNum) { this.role = role; this.name = name; this.imageUrl = imageUrl; this.oauthId = oauthId; this.provider = provider; + this.fcmToken = fcmToken; + this.participations = participations; + this.imageNum = imageNum; } From d9884c950456753c4f52a85e6fc250e0ae5c326c Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 18:06:22 +0900 Subject: [PATCH 22/23] [#-] feat: Implement agenda shortcut feature --- .../org/dnd/timeet/agenda/application/AgendaService.java | 9 ++++++++- src/main/java/org/dnd/timeet/agenda/domain/Agenda.java | 9 +++++++++ .../java/org/dnd/timeet/agenda/domain/AgendaAction.java | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java index 96670fd..37fb47f 100644 --- a/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java +++ b/src/main/java/org/dnd/timeet/agenda/application/AgendaService.java @@ -106,13 +106,20 @@ public AgendaActionResponse changeAgendaStatus(Long meetingId, Long agendaId, Ag case PAUSE -> agenda.pause(); case RESUME -> agenda.resume(); case END -> agenda.complete(); - case MODIFY -> { + case EXTEND -> { LocalTime modifiedDuration = LocalTime.parse(actionRequest.getModifiedDuration()); Duration duration = DurationUtils.convertLocalTimeToDuration(modifiedDuration); agenda.extendDuration(duration); // 회의 시간 추가 addMeetingTotalActualDuration(meetingId, duration); } + case REDUCE -> { + LocalTime modifiedDuration = LocalTime.parse(actionRequest.getModifiedDuration()); + Duration duration = DurationUtils.convertLocalTimeToDuration(modifiedDuration); + agenda.reduceDuration(duration); + // 회의 시간 감소 + subtractMeetingTotalActualDuration(meetingId, duration); + } default -> throw new BadRequestError(BadRequestError.ErrorCode.VALIDATION_FAILED, Collections.singletonMap("Action", "Invalid action")); } diff --git a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java index 5a9fbd7..7020d1a 100644 --- a/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java +++ b/src/main/java/org/dnd/timeet/agenda/domain/Agenda.java @@ -105,6 +105,15 @@ public void extendDuration(Duration extension) { } } + public void reduceDuration(Duration reduction) { + if (this.status != AgendaStatus.COMPLETED && this.allocatedDuration.compareTo(reduction) > 0) { + this.allocatedDuration = this.allocatedDuration.minus(reduction); + } else { + throw new BadRequestError(BadRequestError.ErrorCode.WRONG_REQUEST_TRANSMISSION, + Collections.singletonMap("AgendaDuration", "Invalid duration reduction")); + } + } + public void complete() { validateTransition(AgendaStatus.INPROGRESS, AgendaStatus.PAUSED); diff --git a/src/main/java/org/dnd/timeet/agenda/domain/AgendaAction.java b/src/main/java/org/dnd/timeet/agenda/domain/AgendaAction.java index 595d2c3..0e20000 100644 --- a/src/main/java/org/dnd/timeet/agenda/domain/AgendaAction.java +++ b/src/main/java/org/dnd/timeet/agenda/domain/AgendaAction.java @@ -1,7 +1,7 @@ package org.dnd.timeet.agenda.domain; public enum AgendaAction { - START, PAUSE, RESUME, END, MODIFY; + START, PAUSE, RESUME, END, EXTEND, REDUCE; @Override public String toString() { From d8c9b1d09c1aa04e3a75440e4d31caff8cbfb5f1 Mon Sep 17 00:00:00 2001 From: Mint Date: Wed, 21 Feb 2024 19:25:19 +0900 Subject: [PATCH 23/23] [#-] refactor: Change imageNum type from Long to Integer --- .../org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java | 2 +- src/main/java/org/dnd/timeet/member/domain/Member.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java index d213297..2fb2ab2 100644 --- a/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java +++ b/src/main/java/org/dnd/timeet/meeting/dto/MeetingMemberInfoResponse.java @@ -40,7 +40,7 @@ public static class MeetingMemberDetailResponse { private String nickname; @Schema(description = "이미지 번호(1~12 사이 랜덤)", nullable = false, example = "2") - private Long imageNum; + private Integer imageNum; public MeetingMemberDetailResponse(Member member) { diff --git a/src/main/java/org/dnd/timeet/member/domain/Member.java b/src/main/java/org/dnd/timeet/member/domain/Member.java index 336bef4..5d21993 100644 --- a/src/main/java/org/dnd/timeet/member/domain/Member.java +++ b/src/main/java/org/dnd/timeet/member/domain/Member.java @@ -52,12 +52,12 @@ public class Member extends BaseEntity { private Set participations = new HashSet<>(); @Column(nullable = false, name = "image_num") - private Long imageNum = new Random().nextLong(12) + 1; + private Integer imageNum = new Random().nextInt(12) + 1; @Builder public Member(MemberRole role, String name, String imageUrl, String oauthId, OAuth2Provider provider, String fcmToken, - Set participations, Long imageNum) { + Set participations, Integer imageNum) { this.role = role; this.name = name; this.imageUrl = imageUrl;